

<!DOCTYPE html>
<html lang="zh-CN" data-default-color-scheme=auto>



<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/xt-blog/img/favicon.png">
  <link rel="icon" href="/xt-blog/img/favicon.png">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="author" content="xue tao">
  <meta name="keywords" content="">
  
    <meta name="description" content="Netty一、概述1、什么是Netty12Netty is an asynchronous event-driven network application frameworkfor rapid development of maintainable high performance protocol servers &amp; clients.   Netty 是一个异步的、基于事件驱动的网络应">
<meta property="og:type" content="article">
<meta property="og:title" content="Netty">
<meta property="og:url" content="https://accept_one_time.gitee.io/xt-blog/2022/01/30/Netty/index.html">
<meta property="og:site_name" content="学涛的博客">
<meta property="og:description" content="Netty一、概述1、什么是Netty12Netty is an asynchronous event-driven network application frameworkfor rapid development of maintainable high performance protocol servers &amp; clients.   Netty 是一个异步的、基于事件驱动的网络应">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115324.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115514.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115539.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115605.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115615.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115625.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115634.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115653.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115739.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115811.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115832.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115849.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115904.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115917.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115931.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115946.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130120103.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130120137.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130120204.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130120805.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130120227.png">
<meta property="og:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130120330.png">
<meta property="article:published_time" content="2022-01-30T03:54:10.000Z">
<meta property="article:modified_time" content="2022-01-30T04:20:15.457Z">
<meta property="article:author" content="xue tao">
<meta property="article:tag" content="Netty">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115324.png">
  
  
  <title>Netty - 学涛的博客</title>

  <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4/dist/css/bootstrap.min.css" />


  <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/github-markdown-css@4/github-markdown.min.css" />
  <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hint.css@2/hint.min.css" />

  
    
    
      
      <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@10/styles/github-gist.min.css" />
    
  

  
    <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3/dist/jquery.fancybox.min.css" />
  


<!-- 主题依赖的图标库，不要自行修改 -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_ba1fz6golrf.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_lbnruvf0jn.css">


<link  rel="stylesheet" href="/xt-blog/css/main.css" />

<!-- 自定义样式保持在最底部 -->

  
<link rel="stylesheet" href="/xt-blog/css/mac.css">



  <script id="fluid-configs">
    var Fluid = window.Fluid || {};
    var CONFIG = {"hostname":"accept_one_time.gitee.io","root":"/xt-blog/","version":"1.8.14","typing":{"enable":true,"typeSpeed":70,"cursorChar":"_","loop":false},"anchorjs":{"enable":true,"element":"h1,h2,h3,h4,h5,h6","placement":"right","visible":"hover","icon":""},"progressbar":{"enable":true,"height_px":3,"color":"#29d","options":{"showSpinner":false,"trickleSpeed":100}},"copy_btn":true,"image_zoom":{"enable":true,"img_url_replace":["",""]},"toc":{"enable":true,"headingSelector":"h1,h2,h3,h4,h5,h6","collapseDepth":0},"lazyload":{"enable":true,"loading_img":"/img/loading.gif","onlypost":false,"offset_factor":2},"web_analytics":{"enable":false,"baidu":null,"google":null,"gtag":null,"tencent":{"sid":null,"cid":null},"woyaola":null,"cnzz":null,"leancloud":{"app_id":null,"app_key":null,"server_url":null,"path":"window.location.pathname","ignore_local":false}},"search_path":"/xt-blog/local-search.xml"};
  </script>
  <script  src="/xt-blog/js/utils.js" ></script>
  <script  src="/xt-blog/js/color-schema.js" ></script>
<meta name="generator" content="Hexo 5.4.1"></head>


<body>
  <header style="height: 70vh;">
    <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand" href="/xt-blog/">
      <strong>学涛的博客</strong>
    </a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/xt-blog/">
                <i class="iconfont icon-home-fill"></i>
                首页
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/xt-blog/archives/">
                <i class="iconfont icon-archive-fill"></i>
                文档
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/xt-blog/categories/">
                <i class="iconfont icon-category-fill"></i>
                分类
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/xt-blog/tags/">
                <i class="iconfont icon-tags-fill"></i>
                标签
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/xt-blog/about/">
                <i class="iconfont icon-user-fill"></i>
                关于
              </a>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" target="_self" href="javascript:;" data-toggle="modal" data-target="#modalSearch" aria-label="Search">
              &nbsp;<i class="iconfont icon-search"></i>&nbsp;
            </a>
          </li>
        
        
          <li class="nav-item" id="color-toggle-btn">
            <a class="nav-link" target="_self" href="javascript:;" aria-label="Color Toggle">&nbsp;<i
                class="iconfont icon-dark" id="color-toggle-icon"></i>&nbsp;</a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

    <div class="banner" id="banner" parallax=true
         style="background: url('/xt-blog/img/default.jpg') no-repeat center center;
           background-size: cover;">
      <div class="full-bg-img">
        <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
          <div class="page-header text-center fade-in-up">
            <span class="h2" id="subtitle" title="Netty">
              
            </span>

            
              <div class="mt-3">
  
  
    <span class="post-meta">
      <i class="iconfont icon-date-fill" aria-hidden="true"></i>
      <time datetime="2022-01-30 11:54" pubdate>
        2022年1月30日 中午
      </time>
    </span>
  
</div>

<div class="mt-1">
  
    <span class="post-meta mr-2">
      <i class="iconfont icon-chart"></i>
      137k 字
    </span>
  

  
    <span class="post-meta mr-2">
      <i class="iconfont icon-clock-fill"></i>
      
      
      1140 分钟
    </span>
  

  
  
</div>

            
          </div>

          
            <div class="scroll-down-bar">
              <i class="iconfont icon-arrowdown"></i>
            </div>
          
        </div>
      </div>
    </div>
  </header>

  <main>
    
      

<div class="container-fluid nopadding-x">
  <div class="row nomargin-x">
    <div class="d-none d-lg-block col-lg-2"></div>
    <div class="col-lg-8 nopadding-x-md">
      <div class="container nopadding-x-md" id="board-ctn">
        <div class="py-5" id="board">
          <article class="post-content mx-auto">
            <!-- SEO header -->
            <h1 style="display: none">Netty</h1>
            
            <div class="markdown-body">
              <h1 id="Netty"><a href="#Netty" class="headerlink" title="Netty"></a>Netty</h1><h1 id="一、概述"><a href="#一、概述" class="headerlink" title="一、概述"></a>一、概述</h1><h2 id="1、什么是Netty"><a href="#1、什么是Netty" class="headerlink" title="1、什么是Netty"></a>1、什么是Netty</h2><figure class="highlight applescript"><table><tr><td class="gutter"><div class="code-wrapper"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></div></td><td class="code"><pre><code class="hljs applescript">Netty <span class="hljs-keyword">is</span> an asynchronous event-driven network <span class="hljs-built_in">application</span> framework<br><span class="hljs-keyword">for</span> rapid development <span class="hljs-keyword">of</span> maintainable high performance protocol servers &amp; clients. <br></code></pre></td></tr></table></figure>

<p>Netty 是一个异步的、基于事件驱动的网络应用框架，用于快速开发可维护、高性能的网络服务器和客户端</p>
<p><strong>注意</strong>：<code>netty的异步还是基于多路复用的，并没有实现真正意义上的异步IO</code></p>
<h2 id="2、Netty的优势如果使用传统NIO，其工作量大，bug-多"><a href="#2、Netty的优势如果使用传统NIO，其工作量大，bug-多" class="headerlink" title="2、Netty的优势如果使用传统NIO，其工作量大，bug 多"></a>2、Netty的优势如果使用传统NIO，其工作量大，bug 多</h2><ul>
<li>需要自己构建协议</li>
<li>解决 TCP 传输问题，如粘包、半包</li>
<li>因为bug的存在，epoll 空轮询导致 CPU 100%</li>
</ul>
<p>Netty 对 API 进行增强，使之更易用，如 </p>
<ul>
<li>FastThreadLocal =&gt; ThreadLocal</li>
<li>ByteBuf =&gt; ByteBuffer</li>
</ul>
<h1 id="二、入门案例"><a href="#二、入门案例" class="headerlink" title="二、入门案例"></a>二、入门案例</h1><h2 id="1、服务器端代码"><a href="#1、服务器端代码" class="headerlink" title="1、服务器端代码"></a>1、服务器端代码</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloServer</span> </span>&#123;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;<br>        <span class="hljs-comment">// 1、启动器，负责装配netty组件，启动服务器</span><br>        <span class="hljs-keyword">new</span> ServerBootstrap()<br>                <span class="hljs-comment">// 2、创建 NioEventLoopGroup，可以简单理解为 线程池 + Selector</span><br>                .group(<span class="hljs-keyword">new</span> NioEventLoopGroup())<br>                <span class="hljs-comment">// 3、选择服务器的 ServerSocketChannel 实现</span><br>                .channel(NioServerSocketChannel.class)<br>                <span class="hljs-comment">// 4、child 负责处理读写，该方法决定了 child 执行哪些操作</span><br>            	<span class="hljs-comment">// ChannelInitializer 处理器（仅执行一次）</span><br>            	<span class="hljs-comment">// 它的作用是待客户端SocketChannel建立连接后，执行initChannel以便添加更多的处理器</span><br>                .childHandler(<span class="hljs-keyword">new</span> ChannelInitializer&lt;NioSocketChannel&gt;() &#123;<br>                    <span class="hljs-meta">@Override</span><br>                    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(NioSocketChannel nioSocketChannel)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;<br>                        <span class="hljs-comment">// 5、SocketChannel的处理器，使用StringDecoder解码，ByteBuf=&gt;String</span><br>                        nioSocketChannel.pipeline().addLast(<span class="hljs-keyword">new</span> StringDecoder());<br>                        <span class="hljs-comment">// 6、SocketChannel的业务处理，使用上一个处理器的处理结果</span><br>                        nioSocketChannel.pipeline().addLast(<span class="hljs-keyword">new</span> SimpleChannelInboundHandler&lt;String&gt;() &#123;<br>                            <span class="hljs-meta">@Override</span><br>                            <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead0</span><span class="hljs-params">(ChannelHandlerContext channelHandlerContext, String s)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;<br>                                System.out.println(s);<br>                            &#125;<br>                        &#125;);<br>                    &#125;<br>                    <span class="hljs-comment">// 7、ServerSocketChannel绑定8080端口</span><br>                &#125;).bind(<span class="hljs-number">8080</span>);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h2 id="2、客户端代码"><a href="#2、客户端代码" class="headerlink" title="2、客户端代码"></a>2、客户端代码</h2><figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs aspectj"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloClient</span> </span>&#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> InterruptedException </span>&#123;<br>        <span class="hljs-keyword">new</span> Bootstrap()<br>                .group(<span class="hljs-keyword">new</span> NioEventLoopGroup())<br>                <span class="hljs-comment">// 选择客户 Socket 实现类，NioSocketChannel 表示基于 NIO 的客户端实现</span><br>                .channel(NioSocketChannel.class)<br>                <span class="hljs-comment">// ChannelInitializer 处理器（仅执行一次）</span><br>                <span class="hljs-comment">// 它的作用是待客户端SocketChannel建立连接后，执行initChannel以便添加更多的处理器</span><br>                .<span class="hljs-keyword">handler</span>(<span class="hljs-keyword">new</span> ChannelInitializer&lt;Channel&gt;() &#123;<br>                    <span class="hljs-meta">@Override</span><br>                    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(Channel channel)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;<br>                        <span class="hljs-comment">// 消息会经过通道 handler 处理，这里是将 String =&gt; ByteBuf 编码发出</span><br>                        channel.pipeline().addLast(<span class="hljs-keyword">new</span> StringEncoder());<br>                    &#125;<br>                &#125;)<br>                <span class="hljs-comment">// 指定要连接的服务器和端口</span><br>                .connect(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-string">&quot;localhost&quot;</span>, <span class="hljs-number">8080</span>))<br>                <span class="hljs-comment">// Netty 中很多方法都是异步的，如 connect</span><br>                <span class="hljs-comment">// 这时需要使用 sync 方法等待 connect 建立连接完毕</span><br>                .sync()<br>                <span class="hljs-comment">// 获取 channel 对象，它即为通道抽象，可以进行数据读写操作</span><br>                .channel()<br>                <span class="hljs-comment">// 写入消息并清空缓冲区</span><br>                .writeAndFlush(<span class="hljs-string">&quot;hello world&quot;</span>);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h2 id="3、运行流程"><a href="#3、运行流程" class="headerlink" title="3、运行流程"></a>3、运行流程</h2><p><strong>左：客户端 右：服务器端</strong></p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115324.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<h3 id="组件解释"><a href="#组件解释" class="headerlink" title="组件解释"></a>组件解释</h3><ul>
<li><p>channel 可以理解为数据的通道</p>
</li>
<li><p>msg 理解为流动的数据，最开始输入是 ByteBuf，但经过 pipeline 中的各个 handler 加工，会变成其它类型对象，最后输出又变成 ByteBuf</p>
</li>
<li><p>handler 可以理解为数据的处理工序</p>
<ul>
<li><p>工序有多道，</p>
<p>合在一起就是 pipeline（传递途径）</p>
<p>，pipeline 负责发布事件（读、读取完成…）传播给每个 handler， handler 对自己感兴趣的事件进行处理（重写了相应事件处理方法）</p>
<ul>
<li>pipeline 中有多个 handler，处理时会依次调用其中的 handler</li>
</ul>
</li>
<li><p>handler 分 Inbound 和 Outbound 两类</p>
<ul>
<li>Inbound 入站</li>
<li>Outbound 出站</li>
</ul>
</li>
</ul>
</li>
<li><p>eventLoop 可以理解为处理数据的工人</p>
<ul>
<li>eventLoop 可以管理多个 channel 的 io 操作，并且一旦 eventLoop 负责了某个 channel，就<strong>会将其与channel进行绑定</strong>，以后该 channel 中的 io 操作都由该 eventLoop 负责</li>
<li>eventLoop 既可以执行 io 操作，<strong>也可以进行任务处理</strong>，每个 eventLoop 有自己的任务队列，队列里可以堆放多个 channel 的待处理任务，任务分为普通任务、定时任务</li>
<li>eventLoop 按照 pipeline 顺序，依次按照 handler 的规划（代码）处理数据，可以为每个 handler 指定不同的 eventLoop</li>
</ul>
</li>
</ul>
<h1 id="三、组件"><a href="#三、组件" class="headerlink" title="三、组件"></a>三、组件</h1><h2 id="1、EventLoop"><a href="#1、EventLoop" class="headerlink" title="1、EventLoop"></a>1、EventLoop</h2><p><strong>事件循环对象</strong> EventLoop</p>
<p>EventLoop 本质是一个<strong>单线程执行器</strong>（同时<strong>维护了一个 Selector</strong>），里面有 run 方法处理一个或多个 Channel 上源源不断的 io 事件</p>
<p>它的继承关系如下</p>
<ul>
<li>继承自 j.u.c.ScheduledExecutorService 因此包含了线程池中所有的方法</li>
<li>继承自 netty 自己的 OrderedEventExecutor<ul>
<li>提供了 boolean inEventLoop(Thread thread) 方法判断一个线程是否属于此 EventLoop</li>
<li>提供了 EventLoopGroup parent() 方法来看看自己属于哪个 EventLoopGroup</li>
</ul>
</li>
</ul>
<p><strong>事件循环组</strong> EventLoopGroup</p>
<p>EventLoopGroup 是一组 EventLoop，Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop，后续这个 Channel 上的 io 事件都由此 EventLoop 来处理（保证了 io 事件处理时的线程安全）</p>
<ul>
<li>继承自 netty 自己的 EventExecutorGroup<ul>
<li>实现了 Iterable 接口提供遍历 EventLoop 的能力</li>
<li>另有 next 方法获取集合中下一个 EventLoop</li>
</ul>
</li>
</ul>
<h3 id="处理普通与定时任务"><a href="#处理普通与定时任务" class="headerlink" title="处理普通与定时任务"></a>处理普通与定时任务</h3><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> TestEventLoop &#123;<br>    public static void main(String<span class="hljs-literal">[]</span> args) &#123;<br>        <span class="hljs-comment">// 创建拥有两个EventLoop的NioEventLoopGroup，对应两个线程</span><br>        EventLoopGroup group = <span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup(2)</span>;<br>        <span class="hljs-comment">// 通过next方法可以获得下一个 EventLoop</span><br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(group.next<span class="hljs-literal">()</span>);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(group.next<span class="hljs-literal">()</span>);<br><br>        <span class="hljs-comment">// 通过EventLoop执行普通任务</span><br>        group.next<span class="hljs-literal">()</span>.execute(<span class="hljs-literal">()</span>-&gt;&#123;<br>            <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thread</span>.</span></span>current<span class="hljs-constructor">Thread()</span>.get<span class="hljs-constructor">Name()</span> + <span class="hljs-string">&quot; hello&quot;</span>);<br>        &#125;);<br><br>        <span class="hljs-comment">// 通过EventLoop执行定时任务</span><br>        group.next<span class="hljs-literal">()</span>.schedule<span class="hljs-constructor">AtFixedRate(()</span>-&gt;&#123;<br>            <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thread</span>.</span></span>current<span class="hljs-constructor">Thread()</span>.get<span class="hljs-constructor">Name()</span> + <span class="hljs-string">&quot; hello2&quot;</span>);<br>        &#125;, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, TimeUnit.SECONDS);<br>        <br>        <span class="hljs-comment">// 优雅地关闭</span><br>        group.shutdown<span class="hljs-constructor">Gracefully()</span>;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>输出结果如下</p>
<figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">io</span>.netty.channel.nio.NioEventLoop@<span class="hljs-number">7</span>bb<span class="hljs-number">11784</span><br><span class="hljs-attribute">io</span>.netty.channel.nio.NioEventLoop@<span class="hljs-number">33</span>a<span class="hljs-number">10788</span><br><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">2</span>-<span class="hljs-number">1</span> hello<br><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">2</span>-<span class="hljs-number">2</span> hello<span class="hljs-number">2</span><br><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">2</span>-<span class="hljs-number">2</span> hello<span class="hljs-number">2</span><br><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">2</span>-<span class="hljs-number">2</span> hello<span class="hljs-number">2</span>Copy<br></code></pre></td></tr></table></figure>

<p><strong>关闭 EventLoopGroup</strong></p>
<p>优雅关闭 <code>shutdownGracefully</code> 方法。该方法会首先切换 <code>EventLoopGroup</code> 到关闭状态从而拒绝新的任务的加入，然后在任务队列的任务都处理完成后，停止线程的运行。从而确保整体应用是在正常有序的状态下退出的</p>
<h3 id="处理IO任务"><a href="#处理IO任务" class="headerlink" title="处理IO任务"></a>处理IO任务</h3><h4 id="服务器代码"><a href="#服务器代码" class="headerlink" title="服务器代码"></a>服务器代码</h4><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> MyServer &#123;<br>    public static void main(String<span class="hljs-literal">[]</span> args) &#123;<br>        <span class="hljs-keyword">new</span> <span class="hljs-constructor">ServerBootstrap()</span><br>                .group(<span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup()</span>)<br>                .channel(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">NioServerSocketChannel</span>.</span></span><span class="hljs-keyword">class</span>)<br>                .child<span class="hljs-constructor">Handler(<span class="hljs-params">new</span> ChannelInitializer&lt;SocketChannel&gt;()</span> &#123;<br>                    @Override<br>                    protected void init<span class="hljs-constructor">Channel(SocketChannel <span class="hljs-params">socketChannel</span>)</span> throws Exception &#123;<br>                        socketChannel.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> ChannelInboundHandlerAdapter()</span> &#123;<br>                            @Override<br>                            public void channel<span class="hljs-constructor">Read(ChannelHandlerContext <span class="hljs-params">ctx</span>, Object <span class="hljs-params">msg</span>)</span> throws Exception &#123;<br>                                ByteBuf buf = (ByteBuf) msg;<br>                                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thread</span>.</span></span>current<span class="hljs-constructor">Thread()</span>.get<span class="hljs-constructor">Name()</span> + <span class="hljs-string">&quot; &quot;</span> + buf.<span class="hljs-keyword">to</span><span class="hljs-constructor">String(StandardCharsets.UTF_8)</span>);<br><br>                            &#125;<br>                        &#125;);<br>                    &#125;<br>                &#125;)<br>                .bind(<span class="hljs-number">8080</span>);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h4 id="客户端代码"><a href="#客户端代码" class="headerlink" title="客户端代码"></a>客户端代码</h4><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> MyClient &#123;<br>    public static void main(String<span class="hljs-literal">[]</span> args) throws IOException, InterruptedException &#123;<br>        Channel channel = <span class="hljs-keyword">new</span> <span class="hljs-constructor">Bootstrap()</span><br>                .group(<span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup()</span>)<br>                .channel(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">NioSocketChannel</span>.</span></span><span class="hljs-keyword">class</span>)<br>                .handler(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;<span class="hljs-literal">()</span> &#123;<br>                    @Override<br>                    protected void init<span class="hljs-constructor">Channel(SocketChannel <span class="hljs-params">socketChannel</span>)</span> throws Exception &#123;<br>                        socketChannel.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> StringEncoder()</span>);<br>                    &#125;<br>                &#125;)<br>                .connect(<span class="hljs-keyword">new</span> <span class="hljs-constructor">InetSocketAddress(<span class="hljs-string">&quot;localhost&quot;</span>, 8080)</span>)<br>                .sync<span class="hljs-literal">()</span><br>                .channel<span class="hljs-literal">()</span>;<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(channel);<br>        <span class="hljs-comment">// 此处打断点调试，调用 channel.writeAndFlush(...);</span><br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span><span class="hljs-keyword">in</span>.read<span class="hljs-literal">()</span>;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h3 id="分工"><a href="#分工" class="headerlink" title="分工"></a>分工</h3><p>Bootstrap的group()方法<strong>可以传入两个EventLoopGroup参数</strong>，分别负责处理不同的事件</p>
<figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs haxe"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyServer</span> </span>&#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> void main(<span class="hljs-keyword">String</span>[] args) &#123;<br>        <span class="hljs-keyword">new</span> <span class="hljs-type">ServerBootstrap</span>()<br>            	<span class="hljs-comment">// 两个Group，分别为Boss 负责Accept事件，Worker 负责读写事件</span><br>                .group(<span class="hljs-keyword">new</span> <span class="hljs-type">NioEventLoopGroup</span>(<span class="hljs-number">1</span>), <span class="hljs-keyword">new</span> <span class="hljs-type">NioEventLoopGroup</span>(<span class="hljs-number">2</span>))<br>            <br>				...<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>多个客户端分别发送 <code>hello</code> 结果</p>
<figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">3</span>-<span class="hljs-number">1</span> hello<span class="hljs-number">1</span><br><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">3</span>-<span class="hljs-number">2</span> hello<span class="hljs-number">2</span><br><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">3</span>-<span class="hljs-number">1</span> hello<span class="hljs-number">3</span><br><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">3</span>-<span class="hljs-number">2</span> hello<span class="hljs-number">4</span><br><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">3</span>-<span class="hljs-number">2</span> hello<span class="hljs-number">4</span>Copy<br></code></pre></td></tr></table></figure>

<p>可以看出，一个EventLoop可以<strong>负责多个</strong>Channel，且EventLoop一旦与Channel绑定，则<strong>一直负责</strong>处理该Channel中的事件</p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115514.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<h4 id="增加自定义EventLoopGroup"><a href="#增加自定义EventLoopGroup" class="headerlink" title="增加自定义EventLoopGroup"></a>增加自定义EventLoopGroup</h4><p>当有的<strong>任务需要较长的时间处理时，可以使用非NioEventLoopGroup</strong>，避免同一个NioEventLoop中的其他Channel在较长的时间内都无法得到处理</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> MyServer &#123;<br>    public static void main(String<span class="hljs-literal">[]</span> args) &#123;<br>        <span class="hljs-comment">// 增加自定义的非NioEventLoopGroup</span><br>        EventLoopGroup group = <span class="hljs-keyword">new</span> <span class="hljs-constructor">DefaultEventLoopGroup()</span>;<br>        <br>        <span class="hljs-keyword">new</span> <span class="hljs-constructor">ServerBootstrap()</span><br>                .group(<span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup(1)</span>, <span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup(2)</span>)<br>                .channel(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">NioServerSocketChannel</span>.</span></span><span class="hljs-keyword">class</span>)<br>                .child<span class="hljs-constructor">Handler(<span class="hljs-params">new</span> ChannelInitializer&lt;SocketChannel&gt;()</span> &#123;<br>                    @Override<br>                    protected void init<span class="hljs-constructor">Channel(SocketChannel <span class="hljs-params">socketChannel</span>)</span> throws Exception &#123;<br>                        <span class="hljs-comment">// 增加两个handler，第一个使用NioEventLoopGroup处理，第二个使用自定义EventLoopGroup处理</span><br>                        socketChannel.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-string">&quot;nioHandler&quot;</span>,<span class="hljs-params">new</span> ChannelInboundHandlerAdapter()</span> &#123;<br>                            @Override<br>                            public void channel<span class="hljs-constructor">Read(ChannelHandlerContext <span class="hljs-params">ctx</span>, Object <span class="hljs-params">msg</span>)</span> throws Exception &#123;<br>                                ByteBuf buf = (ByteBuf) msg;<br>                                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thread</span>.</span></span>current<span class="hljs-constructor">Thread()</span>.get<span class="hljs-constructor">Name()</span> + <span class="hljs-string">&quot; &quot;</span> + buf.<span class="hljs-keyword">to</span><span class="hljs-constructor">String(StandardCharsets.UTF_8)</span>);<br>                                <span class="hljs-comment">// 调用下一个handler</span><br>                                ctx.fire<span class="hljs-constructor">ChannelRead(<span class="hljs-params">msg</span>)</span>;<br>                            &#125;<br>                        &#125;)<br>                        <span class="hljs-comment">// 该handler绑定自定义的Group</span><br>                        .add<span class="hljs-constructor">Last(<span class="hljs-params">group</span>, <span class="hljs-string">&quot;myHandler&quot;</span>, <span class="hljs-params">new</span> ChannelInboundHandlerAdapter()</span> &#123;<br>                            @Override<br>                            public void channel<span class="hljs-constructor">Read(ChannelHandlerContext <span class="hljs-params">ctx</span>, Object <span class="hljs-params">msg</span>)</span> throws Exception &#123;<br>                                ByteBuf buf = (ByteBuf) msg;<br>                                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thread</span>.</span></span>current<span class="hljs-constructor">Thread()</span>.get<span class="hljs-constructor">Name()</span> + <span class="hljs-string">&quot; &quot;</span> + buf.<span class="hljs-keyword">to</span><span class="hljs-constructor">String(StandardCharsets.UTF_8)</span>);<br>                            &#125;<br>                        &#125;);<br>                    &#125;<br>                &#125;)<br>                .bind(<span class="hljs-number">8080</span>);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>启动四个客户端发送数据</p>
<figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">4</span>-<span class="hljs-number">1</span> hello<span class="hljs-number">1</span><br><span class="hljs-attribute">defaultEventLoopGroup</span>-<span class="hljs-number">2</span>-<span class="hljs-number">1</span> hello<span class="hljs-number">1</span><br><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">4</span>-<span class="hljs-number">2</span> hello<span class="hljs-number">2</span><br><span class="hljs-attribute">defaultEventLoopGroup</span>-<span class="hljs-number">2</span>-<span class="hljs-number">2</span> hello<span class="hljs-number">2</span><br><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">4</span>-<span class="hljs-number">1</span> hello<span class="hljs-number">3</span><br><span class="hljs-attribute">defaultEventLoopGroup</span>-<span class="hljs-number">2</span>-<span class="hljs-number">3</span> hello<span class="hljs-number">3</span><br><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">4</span>-<span class="hljs-number">2</span> hello<span class="hljs-number">4</span><br><span class="hljs-attribute">defaultEventLoopGroup</span>-<span class="hljs-number">2</span>-<span class="hljs-number">4</span> hello<span class="hljs-number">4</span>Copy<br></code></pre></td></tr></table></figure>

<p>可以看出，客户端与服务器之间的事件，被nioEventLoopGroup和defaultEventLoopGroup分别处理</p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115539.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<h4 id="切换的实现"><a href="#切换的实现" class="headerlink" title="切换的实现"></a>切换的实现</h4><p><strong>不同的EventLoopGroup切换的实现原理如下</strong></p>
<p>由上面的图可以看出，当handler中绑定的Group不同时，需要切换Group来执行不同的任务</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">static void invoke<span class="hljs-constructor">ChannelRead(<span class="hljs-params">final</span> AbstractChannelHandlerContext <span class="hljs-params">next</span>, Object <span class="hljs-params">msg</span>)</span> &#123;<br>    final Object m = next.pipeline.touch(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">ObjectUtil</span>.</span></span>check<span class="hljs-constructor">NotNull(<span class="hljs-params">msg</span>, <span class="hljs-string">&quot;msg&quot;</span>)</span>, next);<br>    <span class="hljs-comment">// 获得下一个EventLoop, excutor 即为 EventLoopGroup</span><br>    EventExecutor executor = next.executor<span class="hljs-literal">()</span>;<br>    <br>    <span class="hljs-comment">// 如果下一个EventLoop 在当前的 EventLoopGroup中</span><br>    <span class="hljs-keyword">if</span> (executor.<span class="hljs-keyword">in</span><span class="hljs-constructor">EventLoop()</span>) &#123;<br>        <span class="hljs-comment">// 使用当前 EventLoopGroup 中的 EventLoop 来处理任务</span><br>        next.invoke<span class="hljs-constructor">ChannelRead(<span class="hljs-params">m</span>)</span>;<br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>        <span class="hljs-comment">// 否则让另一个 EventLoopGroup 中的 EventLoop 来创建任务并执行</span><br>        executor.execute(<span class="hljs-keyword">new</span> <span class="hljs-constructor">Runnable()</span> &#123;<br>            public void run<span class="hljs-literal">()</span> &#123;<br>                next.invoke<span class="hljs-constructor">ChannelRead(<span class="hljs-params">m</span>)</span>;<br>            &#125;<br>        &#125;);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<ul>
<li>如果两个 handler 绑定的是<strong>同一个EventLoopGroup</strong>，那么就直接调用</li>
<li>否则，把要调用的代码封装为一个任务对象，由下一个 handler 的 EventLoopGroup 来调用</li>
</ul>
<h2 id="2、Channel"><a href="#2、Channel" class="headerlink" title="2、Channel"></a>2、Channel</h2><p>Channel 的常用方法</p>
<ul>
<li>close() 可以用来关闭Channel</li>
<li>closeFuture() 用来处理 Channel 的关闭<ul>
<li>sync 方法作用是同步等待 Channel 关闭</li>
<li>而 addListener 方法是异步等待 Channel 关闭</li>
</ul>
</li>
<li>pipeline() 方法用于添加处理器</li>
<li>write() 方法将数据写入<ul>
<li>因为缓冲机制，数据被写入到 Channel 中以后，不会立即被发送</li>
<li><strong>只有当缓冲满了或者调用了flush()方法后</strong>，才会将数据通过 Channel 发送出去</li>
</ul>
</li>
<li>writeAndFlush() 方法将数据写入并<strong>立即发送（刷出）</strong></li>
</ul>
<h3 id="ChannelFuture"><a href="#ChannelFuture" class="headerlink" title="ChannelFuture"></a>ChannelFuture</h3><h4 id="连接问题"><a href="#连接问题" class="headerlink" title="连接问题"></a>连接问题</h4><p><strong>拆分客户端代码</strong></p>
<figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs aspectj"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClient</span> </span>&#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> IOException, InterruptedException </span>&#123;<br>        ChannelFuture channelFuture = <span class="hljs-keyword">new</span> Bootstrap()<br>                .group(<span class="hljs-keyword">new</span> NioEventLoopGroup())<br>                .channel(NioSocketChannel.class)<br>                .<span class="hljs-keyword">handler</span>(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;() &#123;<br>                    <span class="hljs-meta">@Override</span><br>                    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel socketChannel)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;<br>                        socketChannel.pipeline().addLast(<span class="hljs-keyword">new</span> StringEncoder());<br>                    &#125;<br>                &#125;)<br>                <span class="hljs-comment">// 该方法为异步非阻塞方法，主线程调用后不会被阻塞，真正去执行连接操作的是NIO线程</span><br>            	<span class="hljs-comment">// NIO线程：NioEventLoop 中的线程</span><br>                .connect(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-string">&quot;localhost&quot;</span>, <span class="hljs-number">8080</span>));<br>        <br>        <span class="hljs-comment">// 该方法用于等待连接真正建立</span><br>        channelFuture.sync();<br>        <br>        <span class="hljs-comment">// 获取客户端-服务器之间的Channel对象</span><br>        Channel channel = channelFuture.channel();<br>        channel.writeAndFlush(<span class="hljs-string">&quot;hello world&quot;</span>);<br>        System.in.read();<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>如果我们去掉<code>channelFuture.sync()</code>方法，会服务器无法收到<code>hello world</code></p>
<p>这是因为建立连接(connect)的过程是<strong>异步非阻塞</strong>的，若不通过<code>sync()</code>方法阻塞主线程，等待连接真正建立，这时通过 channelFuture.channel() <strong>拿到的 Channel 对象，并不是真正与服务器建立好连接的 Channel</strong>，也就没法将信息正确的传输给服务器端</p>
<p>所以需要通过<code>channelFuture.sync()</code>方法，阻塞主线程，<strong>同步处理结果</strong>，等待连接真正建立好以后，再去获得 Channel 传递数据。使用该方法，获取 Channel 和发送数据的线程<strong>都是主线程</strong></p>
<p>下面还有一种方法，用于<strong>异步</strong>获取建立连接后的 Channel 和发送数据，使得执行这些操作的线程是 NIO 线程（去执行connect操作的线程）</p>
<p><strong>addListener方法</strong></p>
<p>通过这种方法可以<strong>在NIO线程中获取 Channel 并发送数据</strong>，而不是在主线程中执行这些操作</p>
<figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs aspectj"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClient</span> </span>&#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> IOException, InterruptedException </span>&#123;<br>        ChannelFuture channelFuture = <span class="hljs-keyword">new</span> Bootstrap()<br>                .group(<span class="hljs-keyword">new</span> NioEventLoopGroup())<br>                .channel(NioSocketChannel.class)<br>                .<span class="hljs-keyword">handler</span>(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;() &#123;<br>                    <span class="hljs-meta">@Override</span><br>                    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel socketChannel)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;<br>                        socketChannel.pipeline().addLast(<span class="hljs-keyword">new</span> StringEncoder());<br>                    &#125;<br>                &#125;)<br>                <span class="hljs-comment">// 该方法为异步非阻塞方法，主线程调用后不会被阻塞，真正去执行连接操作的是NIO线程</span><br>                <span class="hljs-comment">// NIO线程：NioEventLoop 中的线程</span><br>                .connect(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-string">&quot;localhost&quot;</span>, <span class="hljs-number">8080</span>));<br>        <br>		<span class="hljs-comment">// 当connect方法执行完毕后，也就是连接真正建立后</span><br>        <span class="hljs-comment">// 会在NIO线程中调用operationComplete方法</span><br>        channelFuture.addListener(<span class="hljs-keyword">new</span> ChannelFutureListener() &#123;<br>            <span class="hljs-meta">@Override</span><br>            <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">operationComplete</span><span class="hljs-params">(ChannelFuture channelFuture)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;<br>                Channel channel = channelFuture.channel();<br>                channel.writeAndFlush(<span class="hljs-string">&quot;hello world&quot;</span>);<br>            &#125;<br>        &#125;);<br>        System.in.read();<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h4 id="处理关闭"><a href="#处理关闭" class="headerlink" title="处理关闭"></a>处理关闭</h4><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> ReadClient &#123;<br>    public static void main(String<span class="hljs-literal">[]</span> args) throws InterruptedException &#123;<br>        <span class="hljs-comment">// 创建EventLoopGroup，使用完毕后关闭</span><br>        NioEventLoopGroup group = <span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup()</span>;<br>        <br>        ChannelFuture channelFuture = <span class="hljs-keyword">new</span> <span class="hljs-constructor">Bootstrap()</span><br>                .group(group)<br>                .channel(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">NioSocketChannel</span>.</span></span><span class="hljs-keyword">class</span>)<br>                .handler(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;<span class="hljs-literal">()</span> &#123;<br>                    @Override<br>                    protected void init<span class="hljs-constructor">Channel(SocketChannel <span class="hljs-params">socketChannel</span>)</span> throws Exception &#123;<br>                        socketChannel.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> StringEncoder()</span>);<br>                    &#125;<br>                &#125;)<br>                .connect(<span class="hljs-keyword">new</span> <span class="hljs-constructor">InetSocketAddress(<span class="hljs-string">&quot;localhost&quot;</span>, 8080)</span>);<br>        channelFuture.sync<span class="hljs-literal">()</span>;<br><br>        Channel channel = channelFuture.channel<span class="hljs-literal">()</span>;<br>        Scanner scanner = <span class="hljs-keyword">new</span> <span class="hljs-constructor">Scanner(System.<span class="hljs-params">in</span>)</span>;<br><br>        <span class="hljs-comment">// 创建一个线程用于输入并向服务器发送</span><br>        <span class="hljs-keyword">new</span> <span class="hljs-constructor">Thread(()</span>-&gt;&#123;<br>            <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) &#123;<br>                String msg = scanner.next<span class="hljs-literal">()</span>;<br>                <span class="hljs-keyword">if</span> (<span class="hljs-string">&quot;q&quot;</span>.equals(msg)) &#123;<br>                    <span class="hljs-comment">// 关闭操作是异步的，在NIO线程中执行</span><br>                    channel.close<span class="hljs-literal">()</span>;<br>                    break;<br>                &#125;<br>                channel.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">msg</span>)</span>;<br>            &#125;<br>        &#125;, <span class="hljs-string">&quot;inputThread&quot;</span>).start<span class="hljs-literal">()</span>;<br><br>        <span class="hljs-comment">// 获得closeFuture对象</span><br>        ChannelFuture closeFuture = channel.close<span class="hljs-constructor">Future()</span>;<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;waiting close...&quot;</span>);<br>        <br>        <span class="hljs-comment">// 同步等待NIO线程执行完close操作</span><br>        closeFuture.sync<span class="hljs-literal">()</span>;<br>        <br>        <span class="hljs-comment">// 关闭之后执行一些操作，可以保证执行的操作一定是在channel关闭以后执行的</span><br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;关闭之后执行一些额外操作...&quot;</span>);<br>        <br>        <span class="hljs-comment">// 关闭EventLoopGroup</span><br>        group.shutdown<span class="hljs-constructor">Gracefully()</span>;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>关闭channel</strong></p>
<p>当我们要关闭channel时，可以调用channel.close()方法进行关闭。但是该方法也是一个<strong>异步方法</strong>。真正的关闭操作并不是在调用该方法的线程中执行的，而是<strong>在NIO线程中执行真正的关闭操作</strong></p>
<p>如果我们想在channel<strong>真正关闭以后</strong>，执行一些额外的操作，可以选择以下两种方法来实现</p>
<ul>
<li><p>通过channel.closeFuture()方法获得对应的ChannelFuture对象，然后调用<strong>sync()方法</strong>阻塞执行操作的线程，等待channel真正关闭后，再执行其他操作</p>
<figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs awk"><span class="hljs-regexp">//</span> 获得closeFuture对象<br>ChannelFuture closeFuture = channel.closeFuture();<br><br><span class="hljs-regexp">//</span> 同步等待NIO线程执行完close操作<br>closeFuture.sync();Copy<br></code></pre></td></tr></table></figure></li>
<li><p>调用<strong>closeFuture.addListener</strong>方法，添加close的后续操作</p>
<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs gradle">closeFuture.addListener(<span class="hljs-keyword">new</span> ChannelFutureListener() &#123;<br>    @Override<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> operationComplete(ChannelFuture channelFuture) <span class="hljs-keyword">throws</span> Exception &#123;<br>        <span class="hljs-comment">// 等待channel关闭后才执行的操作</span><br>        System.out.<span class="hljs-keyword">println</span>(<span class="hljs-string">&quot;关闭之后执行一些额外操作...&quot;</span>);<br>        <span class="hljs-comment">// 关闭EventLoopGroup</span><br>        <span class="hljs-keyword">group</span>.shutdownGracefully();<br>    &#125;<br>&#125;);<span class="hljs-keyword">Copy</span><br></code></pre></td></tr></table></figure></li>
</ul>
<h2 id="3、Future与Promise"><a href="#3、Future与Promise" class="headerlink" title="3、Future与Promise"></a>3、Future与Promise</h2><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><p>netty 中的 Future 与 jdk 中的 Future <strong>同名</strong>，但是是两个接口</p>
<p>netty 的 Future 继承自 jdk 的 Future，而 Promise 又对 netty Future 进行了扩展</p>
<ul>
<li>jdk Future 只能同步等待任务结束（或成功、或失败）才能得到结果</li>
<li>netty Future 可以同步等待任务结束得到结果，也可以异步方式得到结果，但<strong>都是要等任务结束</strong></li>
<li>netty Promise 不仅有 netty Future 的功能，而且脱离了任务独立存在，<strong>只作为两个线程间传递结果的容器</strong></li>
</ul>
<table>
<thead>
<tr>
<th>功能/名称</th>
<th>jdk Future</th>
<th>netty Future</th>
<th>Promise</th>
</tr>
</thead>
<tbody><tr>
<td>cancel</td>
<td>取消任务</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>isCanceled</td>
<td>任务是否取消</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>isDone</td>
<td>任务是否完成，不能区分成功失败</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>get</td>
<td>获取任务结果，阻塞等待</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>getNow</td>
<td>-</td>
<td>获取任务结果，非阻塞，还未产生结果时返回 null</td>
<td>-</td>
</tr>
<tr>
<td>await</td>
<td>-</td>
<td>等待任务结束，如果任务失败，<strong>不会抛异常</strong>，而是通过 isSuccess 判断</td>
<td>-</td>
</tr>
<tr>
<td>sync</td>
<td>-</td>
<td>等待任务结束，如果任务失败，抛出异常</td>
<td>-</td>
</tr>
<tr>
<td>isSuccess</td>
<td>-</td>
<td>判断任务是否成功</td>
<td>-</td>
</tr>
<tr>
<td>cause</td>
<td>-</td>
<td>获取失败信息，非阻塞，如果没有失败，返回null</td>
<td>-</td>
</tr>
<tr>
<td>addLinstener</td>
<td>-</td>
<td>添加回调，异步接收结果</td>
<td>-</td>
</tr>
<tr>
<td>setSuccess</td>
<td>-</td>
<td>-</td>
<td>设置成功结果</td>
</tr>
<tr>
<td>setFailure</td>
<td>-</td>
<td>-</td>
<td>设置失败结果</td>
</tr>
</tbody></table>
<h3 id="JDK-Future"><a href="#JDK-Future" class="headerlink" title="JDK Future"></a>JDK Future</h3><figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs haxe"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JdkFuture</span> </span>&#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> void main(<span class="hljs-keyword">String</span>[] args) throws ExecutionException, InterruptedException &#123;<br>        ThreadFactory factory = <span class="hljs-keyword">new</span> <span class="hljs-type">ThreadFactory</span>() &#123;<br>            @Override<br>            <span class="hljs-keyword">public</span> Thread <span class="hljs-keyword">new</span><span class="hljs-type">Thread</span>(Runnable r) &#123;<br>                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-type">Thread</span>(r, <span class="hljs-string">&quot;JdkFuture&quot;</span>);<br>            &#125;<br>        &#125;;<br>        <span class="hljs-comment">// 创建线程池</span><br>        ThreadPoolExecutor executor = <span class="hljs-keyword">new</span> <span class="hljs-type">ThreadPoolExecutor</span>(<span class="hljs-number">5</span>, <span class="hljs-number">10</span>,<span class="hljs-number">10</span>, TimeUnit.SECONDS, <span class="hljs-keyword">new</span> <span class="hljs-type">ArrayBlockingQueue</span>&lt;&gt;(<span class="hljs-number">10</span>), factory);<br><br>        <span class="hljs-comment">// 获得Future对象</span><br>        Future&lt;Integer&gt; future = executor.submit(<span class="hljs-keyword">new</span> <span class="hljs-type">Callable</span>&lt;Integer&gt;() &#123;<br><br>            @Override<br>            <span class="hljs-keyword">public</span> Integer call() throws Exception &#123;<br>                TimeUnit.SECONDS.sleep(<span class="hljs-number">1</span>);<br>                <span class="hljs-keyword">return</span> <span class="hljs-number">50</span>;<br>            &#125;<br>        &#125;);<br><br>        <span class="hljs-comment">// 通过阻塞的方式，获得运行结果</span><br>        System.out.println(future.<span class="hljs-keyword">get</span>());<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h3 id="Netty-Future"><a href="#Netty-Future" class="headerlink" title="Netty Future"></a>Netty Future</h3><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> NettyFuture &#123;<br>    public static void main(String<span class="hljs-literal">[]</span> args) throws ExecutionException, InterruptedException &#123;<br>        NioEventLoopGroup group = <span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup()</span>;<br><br>        <span class="hljs-comment">// 获得 EventLoop 对象</span><br>        EventLoop eventLoop = group.next<span class="hljs-literal">()</span>;<br>        Future&lt;Integer&gt; future = eventLoop.submit(<span class="hljs-keyword">new</span> Callable&lt;Integer&gt;<span class="hljs-literal">()</span> &#123;<br>            @Override<br>            public Integer call<span class="hljs-literal">()</span> throws Exception &#123;<br>                return <span class="hljs-number">50</span>;<br>            &#125;<br>        &#125;);<br><br>        <span class="hljs-comment">// 主线程中获取结果</span><br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thread</span>.</span></span>current<span class="hljs-constructor">Thread()</span>.get<span class="hljs-constructor">Name()</span> + <span class="hljs-string">&quot; 获取结果&quot;</span>);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;getNow &quot;</span> + future.get<span class="hljs-constructor">Now()</span>);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;get &quot;</span> + future.get<span class="hljs-literal">()</span>);<br><br>        <span class="hljs-comment">// NIO线程中异步获取结果</span><br>        future.add<span class="hljs-constructor">Listener(<span class="hljs-params">new</span> GenericFutureListener&lt;Future&lt;? <span class="hljs-params">super</span> Integer&gt;&gt;()</span> &#123;<br>            @Override<br>            public void operation<span class="hljs-constructor">Complete(Future&lt;? <span class="hljs-params">super</span> Integer&gt; <span class="hljs-params">future</span>)</span> throws Exception &#123;<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thread</span>.</span></span>current<span class="hljs-constructor">Thread()</span>.get<span class="hljs-constructor">Name()</span> + <span class="hljs-string">&quot; 获取结果&quot;</span>);<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;getNow &quot;</span> + future.get<span class="hljs-constructor">Now()</span>);<br>            &#125;<br>        &#125;);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>运行结果</strong></p>
<figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">main</span> 获取结果<br><span class="hljs-attribute">getNow</span> null<br><span class="hljs-attribute">get</span> <span class="hljs-number">50</span><br><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">2</span>-<span class="hljs-number">1</span> 获取结果<br><span class="hljs-attribute">getNow</span> <span class="hljs-number">50</span>Copy<br></code></pre></td></tr></table></figure>

<p>Netty中的Future对象，可以通过EventLoop的sumbit()方法得到</p>
<ul>
<li>可以通过Future对象的<strong>get方法</strong>，阻塞地获取返回结果</li>
<li>也可以通过<strong>getNow方法</strong>，获取结果，若还没有结果，则返回null，该方法是非阻塞的</li>
<li>还可以通过<strong>future.addListener方法</strong>，在Callable方法执行的线程中，异步获取返回结果</li>
</ul>
<h3 id="Netty-Promise"><a href="#Netty-Promise" class="headerlink" title="Netty Promise"></a>Netty Promise</h3><p>Promise相当于一个容器，可以用于存放各个线程中的结果，然后让其他线程去获取该结果</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> NettyPromise &#123;<br>    public static void main(String<span class="hljs-literal">[]</span> args) throws ExecutionException, InterruptedException &#123;<br>        <span class="hljs-comment">// 创建EventLoop</span><br>        NioEventLoopGroup group = <span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup()</span>;<br>        EventLoop eventLoop = group.next<span class="hljs-literal">()</span>;<br><br>        <span class="hljs-comment">// 创建Promise对象，用于存放结果</span><br>        DefaultPromise&lt;Integer&gt; promise = <span class="hljs-keyword">new</span> DefaultPromise&lt;&gt;(eventLoop);<br><br>        <span class="hljs-keyword">new</span> <span class="hljs-constructor">Thread(()</span>-&gt;&#123;<br>            <span class="hljs-keyword">try</span> &#123;<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">TimeUnit</span>.</span><span class="hljs-module"><span class="hljs-identifier">SECONDS</span>.</span></span>sleep(<span class="hljs-number">1</span>);<br>            &#125; catch (InterruptedException e) &#123;<br>                e.print<span class="hljs-constructor">StackTrace()</span>;<br>            &#125;<br>            <span class="hljs-comment">// 自定义线程向Promise中存放结果</span><br>            promise.set<span class="hljs-constructor">Success(50)</span>;<br>        &#125;).start<span class="hljs-literal">()</span>;<br><br>        <span class="hljs-comment">// 主线程从Promise中获取结果</span><br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thread</span>.</span></span>current<span class="hljs-constructor">Thread()</span>.get<span class="hljs-constructor">Name()</span> + <span class="hljs-string">&quot; &quot;</span> + promise.get<span class="hljs-literal">()</span>);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h2 id="4、Handler与Pipeline"><a href="#4、Handler与Pipeline" class="headerlink" title="4、Handler与Pipeline"></a>4、Handler与Pipeline</h2><h3 id="Pipeline"><a href="#Pipeline" class="headerlink" title="Pipeline"></a>Pipeline</h3><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> PipeLineServer &#123;<br>    public static void main(String<span class="hljs-literal">[]</span> args) &#123;<br>        <span class="hljs-keyword">new</span> <span class="hljs-constructor">ServerBootstrap()</span><br>                .group(<span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup()</span>)<br>                .channel(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">NioServerSocketChannel</span>.</span></span><span class="hljs-keyword">class</span>)<br>                .child<span class="hljs-constructor">Handler(<span class="hljs-params">new</span> ChannelInitializer&lt;SocketChannel&gt;()</span> &#123;<br>                    @Override<br>                    protected void init<span class="hljs-constructor">Channel(SocketChannel <span class="hljs-params">socketChannel</span>)</span> throws Exception &#123;<br>                        <span class="hljs-comment">// 在socketChannel的pipeline中添加handler</span><br>                        <span class="hljs-comment">// pipeline中handler是带有head与tail节点的双向链表，的实际结构为</span><br>    				 	<span class="hljs-comment">// head &lt;-&gt; handler1 &lt;-&gt; ... &lt;-&gt; handler4 &lt;-&gt;tail</span><br>                        <span class="hljs-comment">// Inbound主要处理入站操作，一般为读操作，发生入站操作时会触发Inbound方法</span><br>                        <span class="hljs-comment">// 入站时，handler是从head向后调用的</span><br>                        socketChannel.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-string">&quot;handler1&quot;</span> ,<span class="hljs-params">new</span> ChannelInboundHandlerAdapter()</span> &#123;<br>                            @Override<br>                            public void channel<span class="hljs-constructor">Read(ChannelHandlerContext <span class="hljs-params">ctx</span>, Object <span class="hljs-params">msg</span>)</span> throws Exception &#123;<br>                                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thread</span>.</span></span>current<span class="hljs-constructor">Thread()</span>.get<span class="hljs-constructor">Name()</span> + <span class="hljs-string">&quot; Inbound handler 1&quot;</span>);<br>                                <span class="hljs-comment">// 父类该方法内部会调用fireChannelRead</span><br>                                <span class="hljs-comment">// 将数据传递给下一个handler</span><br>                                super.channel<span class="hljs-constructor">Read(<span class="hljs-params">ctx</span>, <span class="hljs-params">msg</span>)</span>;<br>                            &#125;<br>                        &#125;);<br>                        socketChannel.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-string">&quot;handler2&quot;</span>, <span class="hljs-params">new</span> ChannelInboundHandlerAdapter()</span> &#123;<br>                            @Override<br>                            public void channel<span class="hljs-constructor">Read(ChannelHandlerContext <span class="hljs-params">ctx</span>, Object <span class="hljs-params">msg</span>)</span> throws Exception &#123;<br>                                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thread</span>.</span></span>current<span class="hljs-constructor">Thread()</span>.get<span class="hljs-constructor">Name()</span> + <span class="hljs-string">&quot; Inbound handler 2&quot;</span>);<br>                                <span class="hljs-comment">// 执行write操作，使得Outbound的方法能够得到调用</span><br>          socketChannel.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">ctx</span>.<span class="hljs-params">alloc</span>()</span>.buffer<span class="hljs-literal">()</span>.write<span class="hljs-constructor">Bytes(<span class="hljs-string">&quot;Server...&quot;</span>.<span class="hljs-params">getBytes</span>(StandardCharsets.UTF_8)</span>));<br>                                super.channel<span class="hljs-constructor">Read(<span class="hljs-params">ctx</span>, <span class="hljs-params">msg</span>)</span>;<br>                            &#125;<br>                        &#125;);<br>                        <span class="hljs-comment">// Outbound主要处理出站操作，一般为写操作，发生出站操作时会触发Outbound方法</span><br>                        <span class="hljs-comment">// 出站时，handler的调用是从tail向前调用的</span><br>                        socketChannel.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-string">&quot;handler3&quot;</span> ,<span class="hljs-params">new</span> ChannelOutboundHandlerAdapter()</span>&#123;<br>                            @Override<br>                            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception &#123;<br>                                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thread</span>.</span></span>current<span class="hljs-constructor">Thread()</span>.get<span class="hljs-constructor">Name()</span> + <span class="hljs-string">&quot; Outbound handler 1&quot;</span>);<br>                                super.write(ctx, msg, promise);<br>                            &#125;<br>                        &#125;);<br>                        socketChannel.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-string">&quot;handler4&quot;</span> ,<span class="hljs-params">new</span> ChannelOutboundHandlerAdapter()</span>&#123;<br>                            @Override<br>                            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception &#123;<br>                                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thread</span>.</span></span>current<span class="hljs-constructor">Thread()</span>.get<span class="hljs-constructor">Name()</span> + <span class="hljs-string">&quot; Outbound handler 2&quot;</span>);<br>                                super.write(ctx, msg, promise);<br>                            &#125;<br>                        &#125;);<br>                    &#125;<br>                &#125;)<br>                .bind(<span class="hljs-number">8080</span>);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>运行结果如下</strong></p>
<figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">2</span>-<span class="hljs-number">2</span> Inbound handler <span class="hljs-number">1</span><br><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">2</span>-<span class="hljs-number">2</span> Inbound handler <span class="hljs-number">2</span><br><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">2</span>-<span class="hljs-number">2</span> Outbound handler <span class="hljs-number">2</span><br><span class="hljs-attribute">nioEventLoopGroup</span>-<span class="hljs-number">2</span>-<span class="hljs-number">2</span> Outbound handler <span class="hljs-number">1</span>Copy<br></code></pre></td></tr></table></figure>

<p>通过channel.pipeline().addLast(name, handler)添加handler时，<strong>记得给handler取名字</strong>。这样可以调用pipeline的<strong>addAfter、addBefore等方法更灵活地向pipeline中添加handler</strong></p>
<p>handler需要放入通道的pipeline中，才能根据放入顺序来使用handler</p>
<ul>
<li>pipeline是结构是一个带有head与tail指针的双向链表，其中的节点为handler<ul>
<li>要通过ctx.fireChannelRead(msg)等方法，<strong>将当前handler的处理结果传递给下一个handler</strong></li>
</ul>
</li>
<li>当有<strong>入站</strong>（Inbound）操作时，会从<strong>head开始向后</strong>调用handler，直到handler不是处理Inbound操作为止</li>
<li>当有<strong>出站</strong>（Outbound）操作时，会从<strong>tail开始向前</strong>调用handler，直到handler不是处理Outbound操作为止</li>
</ul>
<p><strong>具体结构如下</strong></p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115605.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<p><strong>调用顺序如下</strong></p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115615.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<h3 id="OutboundHandler"><a href="#OutboundHandler" class="headerlink" title="OutboundHandler"></a>OutboundHandler</h3><h4 id="socketChannel-writeAndFlush"><a href="#socketChannel-writeAndFlush" class="headerlink" title="socketChannel.writeAndFlush()"></a>socketChannel.writeAndFlush()</h4><p>当handler中调用该方法进行写操作时，会触发Outbound操作，<strong>此时是从tail向前寻找OutboundHandler</strong></p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115625.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<h4 id="ctx-writeAndFlush"><a href="#ctx-writeAndFlush" class="headerlink" title="ctx.writeAndFlush()"></a>ctx.writeAndFlush()</h4><p>当handler中调用该方法进行写操作时，会触发Outbound操作，<strong>此时是从当前handler向前寻找OutboundHandler</strong></p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115634.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<h3 id="EmbeddedChannel"><a href="#EmbeddedChannel" class="headerlink" title="EmbeddedChannel"></a>EmbeddedChannel</h3><p>EmbeddedChannel可以用于测试各个handler，通过其构造函数按顺序传入需要测试handler，然后调用对应的Inbound和Outbound方法即可</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> TestEmbeddedChannel &#123;<br>    public static void main(String<span class="hljs-literal">[]</span> args) &#123;<br>        ChannelInboundHandlerAdapter h1 = <span class="hljs-keyword">new</span> <span class="hljs-constructor">ChannelInboundHandlerAdapter()</span> &#123;<br>            @Override<br>            public void channel<span class="hljs-constructor">Read(ChannelHandlerContext <span class="hljs-params">ctx</span>, Object <span class="hljs-params">msg</span>)</span> throws Exception &#123;<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;1&quot;</span>);<br>                super.channel<span class="hljs-constructor">Read(<span class="hljs-params">ctx</span>, <span class="hljs-params">msg</span>)</span>;<br>            &#125;<br>        &#125;;<br><br>        ChannelInboundHandlerAdapter h2 = <span class="hljs-keyword">new</span> <span class="hljs-constructor">ChannelInboundHandlerAdapter()</span> &#123;<br>            @Override<br>            public void channel<span class="hljs-constructor">Read(ChannelHandlerContext <span class="hljs-params">ctx</span>, Object <span class="hljs-params">msg</span>)</span> throws Exception &#123;<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;2&quot;</span>);<br>                super.channel<span class="hljs-constructor">Read(<span class="hljs-params">ctx</span>, <span class="hljs-params">msg</span>)</span>;<br>            &#125;<br>        &#125;;<br><br>        ChannelOutboundHandlerAdapter h3 = <span class="hljs-keyword">new</span> <span class="hljs-constructor">ChannelOutboundHandlerAdapter()</span> &#123;<br>            @Override<br>            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception &#123;<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;3&quot;</span>);<br>                super.write(ctx, msg, promise);<br>            &#125;<br>        &#125;;<br><br>        ChannelOutboundHandlerAdapter h4 = <span class="hljs-keyword">new</span> <span class="hljs-constructor">ChannelOutboundHandlerAdapter()</span> &#123;<br>            @Override<br>            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception &#123;<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;4&quot;</span>);<br>                super.write(ctx, msg, promise);<br>            &#125;<br>        &#125;;<br><br>        <span class="hljs-comment">// 用于测试Handler的Channel</span><br>        EmbeddedChannel channel = <span class="hljs-keyword">new</span> <span class="hljs-constructor">EmbeddedChannel(<span class="hljs-params">h1</span>, <span class="hljs-params">h2</span>, <span class="hljs-params">h3</span>, <span class="hljs-params">h4</span>)</span>;<br>        <br>        <span class="hljs-comment">// 执行Inbound操作 </span><br>        channel.write<span class="hljs-constructor">Inbound(ByteBufAllocator.DEFAULT.<span class="hljs-params">buffer</span>()</span>.write<span class="hljs-constructor">Bytes(<span class="hljs-string">&quot;hello&quot;</span>.<span class="hljs-params">getBytes</span>(StandardCharsets.UTF_8)</span>));<br>        <span class="hljs-comment">// 执行Outbound操作</span><br>        channel.write<span class="hljs-constructor">Outbound(ByteBufAllocator.DEFAULT.<span class="hljs-params">buffer</span>()</span>.write<span class="hljs-constructor">Bytes(<span class="hljs-string">&quot;hello&quot;</span>.<span class="hljs-params">getBytes</span>(StandardCharsets.UTF_8)</span>));<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h2 id="5、ByteBuf"><a href="#5、ByteBuf" class="headerlink" title="5、ByteBuf"></a>5、ByteBuf</h2><p><strong>调试工具方法</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-keyword">private</span> static void log(ByteBuf buffer) &#123;<br>    <span class="hljs-built_in">int</span> length = buffer.readable<span class="hljs-constructor">Bytes()</span>;<br>    <span class="hljs-built_in">int</span> rows = length<span class="hljs-operator"> / </span><span class="hljs-number">16</span> + (length % <span class="hljs-number">15</span><span class="hljs-operator"> == </span><span class="hljs-number">0</span> ? <span class="hljs-number">0</span> : <span class="hljs-number">1</span>) + <span class="hljs-number">4</span>;<br>    StringBuilder buf = <span class="hljs-keyword">new</span> <span class="hljs-constructor">StringBuilder(<span class="hljs-params">rows</span> <span class="hljs-operator">*</span> 80 <span class="hljs-operator">*</span> 2)</span><br>        .append(<span class="hljs-string">&quot;read index:&quot;</span>).append(buffer.reader<span class="hljs-constructor">Index()</span>)<br>        .append(<span class="hljs-string">&quot; write index:&quot;</span>).append(buffer.writer<span class="hljs-constructor">Index()</span>)<br>        .append(<span class="hljs-string">&quot; capacity:&quot;</span>).append(buffer.capacity<span class="hljs-literal">()</span>)<br>        .append(NEWLINE);<br>    append<span class="hljs-constructor">PrettyHexDump(<span class="hljs-params">buf</span>, <span class="hljs-params">buffer</span>)</span>;<br>    <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(buf.<span class="hljs-keyword">to</span><span class="hljs-constructor">String()</span>);<br>&#125; <br></code></pre></td></tr></table></figure>

<p>该方法可以帮助我们更为详细地查看ByteBuf中的内容</p>
<h3 id="创建"><a href="#创建" class="headerlink" title="创建"></a>创建</h3><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs arduino"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ByteBufStudy</span> &#123;</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">String</span>[] args)</span> </span>&#123;<br>        <span class="hljs-comment">// 创建ByteBuf</span><br>        ByteBuf buffer = ByteBufAllocator.<span class="hljs-literal">DEFAULT</span>.<span class="hljs-built_in">buffer</span>(<span class="hljs-number">16</span>);<br>        ByteBufUtil.<span class="hljs-built_in">log</span>(buffer);<br><br>        <span class="hljs-comment">// 向buffer中写入数据</span><br>        StringBuilder sb = <span class="hljs-keyword">new</span> <span class="hljs-built_in">StringBuilder</span>();<br>        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">20</span>; i++) &#123;<br>            sb.<span class="hljs-built_in">append</span>(<span class="hljs-string">&quot;a&quot;</span>);<br>        &#125;<br>        buffer.<span class="hljs-built_in">writeBytes</span>(sb.<span class="hljs-built_in">toString</span>().<span class="hljs-built_in">getBytes</span>(StandardCharsets.UTF_8));<br><br>        <span class="hljs-comment">// 查看写入结果</span><br>        ByteBufUtil.<span class="hljs-built_in">log</span>(buffer);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>运行结果</strong></p>
<figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs tap">read index:0 write index:0 capacity:16<br><br>read index:0 write index:20 capacity:64<br>         +-------------------------------------------------+<br>         | <span class="hljs-number"> 0 </span><span class="hljs-number"> 1 </span><span class="hljs-number"> 2 </span><span class="hljs-number"> 3 </span><span class="hljs-number"> 4 </span><span class="hljs-number"> 5 </span><span class="hljs-number"> 6 </span><span class="hljs-number"> 7 </span><span class="hljs-number"> 8 </span><span class="hljs-number"> 9 </span> a  b  c  d  e  f |<br>+--------+-------------------------------------------------+----------------+<br>|00000000|<span class="hljs-number"> 61 </span>61<span class="hljs-number"> 61 </span>61<span class="hljs-number"> 61 </span>61<span class="hljs-number"> 61 </span>61<span class="hljs-number"> 61 </span>61<span class="hljs-number"> 61 </span>61<span class="hljs-number"> 61 </span>61<span class="hljs-number"> 61 </span>61 |aaaaaaaaaaaaaaaa|<br>|00000010|<span class="hljs-number"> 61 </span>61<span class="hljs-number"> 61 </span>61                                     |aaaa            |<br>+--------+-------------------------------------------------+----------------+ <br></code></pre></td></tr></table></figure>

<p>ByteBuf<strong>通过<code>ByteBufAllocator</code>选择allocator并调用对应的buffer()方法来创建的</strong>，默认使用<strong>直接内存</strong>作为ByteBuf，容量为256个字节，可以指定初始容量的大小</p>
<p>当ByteBuf的容量无法容纳所有数据时，<strong>ByteBuf会进行扩容操作</strong></p>
<p><strong>如果在handler中创建ByteBuf，建议使用<code>ChannelHandlerContext ctx.alloc().buffer()</code>来创建</strong></p>
<h3 id="直接内存与堆内存"><a href="#直接内存与堆内存" class="headerlink" title="直接内存与堆内存"></a>直接内存与堆内存</h3><p>通过该方法创建的ByteBuf，使用的是<strong>基于直接内存</strong>的ByteBuf</p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs arduino">ByteBuf buffer = ByteBufAllocator.<span class="hljs-literal">DEFAULT</span>.<span class="hljs-built_in">buffer</span>(<span class="hljs-number">16</span>);Copy<br></code></pre></td></tr></table></figure>

<p>可以使用下面的代码来创建池化<strong>基于堆</strong>的 ByteBuf</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">ByteBuf buffer = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">ByteBufAllocator</span>.</span><span class="hljs-module"><span class="hljs-identifier">DEFAULT</span>.</span></span>heap<span class="hljs-constructor">Buffer(16)</span>;Copy<br></code></pre></td></tr></table></figure>

<p>也可以使用下面的代码来创建池化<strong>基于直接内存</strong>的 ByteBuf</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">ByteBuf buffer = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">ByteBufAllocator</span>.</span><span class="hljs-module"><span class="hljs-identifier">DEFAULT</span>.</span></span>direct<span class="hljs-constructor">Buffer(16)</span>;Copy<br></code></pre></td></tr></table></figure>

<ul>
<li>直接内存创建和销毁的代价昂贵，但读写性能高（少一次内存复制），适合配合池化功能一起用</li>
<li>直接内存对 GC 压力小，因为这部分内存不受 JVM 垃圾回收的管理，但也要注意及时主动释放</li>
</ul>
<p><strong>验证</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> ByteBufStudy &#123;<br>    public static void main(String<span class="hljs-literal">[]</span> args) &#123;<br>        ByteBuf buffer = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">ByteBufAllocator</span>.</span><span class="hljs-module"><span class="hljs-identifier">DEFAULT</span>.</span></span>buffer(<span class="hljs-number">16</span>);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(buffer.get<span class="hljs-constructor">Class()</span>);<br><br>        buffer = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">ByteBufAllocator</span>.</span><span class="hljs-module"><span class="hljs-identifier">DEFAULT</span>.</span></span>heap<span class="hljs-constructor">Buffer(16)</span>;<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(buffer.get<span class="hljs-constructor">Class()</span>);<br><br>        buffer = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">ByteBufAllocator</span>.</span><span class="hljs-module"><span class="hljs-identifier">DEFAULT</span>.</span></span>direct<span class="hljs-constructor">Buffer(16)</span>;<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(buffer.get<span class="hljs-constructor">Class()</span>);<br>    &#125;<br>&#125; <br><span class="hljs-comment">// 使用池化的直接内存</span><br><span class="hljs-keyword">class</span> io.netty.buffer.PooledUnsafeDirectByteBuf<br>    <br><span class="hljs-comment">// 使用池化的堆内存    </span><br><span class="hljs-keyword">class</span> io.netty.buffer.PooledUnsafeHeapByteBuf<br>    <br><span class="hljs-comment">// 使用池化的直接内存    </span><br><span class="hljs-keyword">class</span> io.netty.buffer.PooledUnsafeDirectByteBufCopy<br></code></pre></td></tr></table></figure>

<h3 id="池化与非池化"><a href="#池化与非池化" class="headerlink" title="池化与非池化"></a>池化与非池化</h3><p>池化的最大意义在于可以<strong>重用</strong> ByteBuf，优点有</p>
<ul>
<li>没有池化，则每次都得创建新的 ByteBuf 实例，这个操作对直接内存代价昂贵，就算是堆内存，也会增加 GC 压力</li>
<li>有了池化，则可以重用池中 ByteBuf 实例，并且采用了与 jemalloc 类似的内存分配算法提升分配效率</li>
<li>高并发时，池化功能更节约内存，减少内存溢出的可能</li>
</ul>
<p>池化功能是否开启，可以通过下面的系统环境变量来设置</p>
<figure class="highlight elm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs elm">-<span class="hljs-type">Dio</span>.netty.allocator.<span class="hljs-keyword">type</span>=&#123;unpooled|pooled&#125; <br></code></pre></td></tr></table></figure>

<ul>
<li>4.1 以后，<strong>非 Android 平台默认启用池化实现</strong>，Android 平台启用非池化实现</li>
<li>4.1 之前，池化功能还不成熟，默认是非池化实现</li>
</ul>
<h3 id="组成"><a href="#组成" class="headerlink" title="组成"></a>组成</h3><p>ByteBuf主要有以下几个组成部分</p>
<ul>
<li><p>最大容量与当前容量</p>
<ul>
<li>在构造ByteBuf时，可传入两个参数，分别代表初始容量和最大容量，若未传入第二个参数（最大容量），最大容量默认为Integer.MAX_VALUE</li>
<li>当ByteBuf容量无法容纳所有数据时，会进行扩容操作，若<strong>超出最大容量</strong>，会抛出<code>java.lang.IndexOutOfBoundsException</code>异常</li>
</ul>
</li>
<li><p>读写操作不同于ByteBuffer只用position进行控制，</p>
<p>ByteBuf分别由读指针和写指针两个指针控制</p>
<p>。进行读写操作时，无需进行模式的切换</p>
<ul>
<li>读指针前的部分被称为废弃部分，是已经读过的内容</li>
<li>读指针与写指针之间的空间称为可读部分</li>
<li>写指针与当前容量之间的空间称为可写部分</li>
</ul>
</li>
</ul>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115653.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<h3 id="写入"><a href="#写入" class="headerlink" title="写入"></a>写入</h3><p>常用方法如下</p>
<table>
<thead>
<tr>
<th>方法签名</th>
<th>含义</th>
<th>备注</th>
</tr>
</thead>
<tbody><tr>
<td>writeBoolean(boolean value)</td>
<td>写入 boolean 值</td>
<td><strong>用一字节 01|00 代表 true|false</strong></td>
</tr>
<tr>
<td>writeByte(int value)</td>
<td>写入 byte 值</td>
<td></td>
</tr>
<tr>
<td>writeShort(int value)</td>
<td>写入 short 值</td>
<td></td>
</tr>
<tr>
<td>writeInt(int value)</td>
<td>写入 int 值</td>
<td>Big Endian（大端写入），即 0x250，写入后 00 00 02 50</td>
</tr>
<tr>
<td>writeIntLE(int value)</td>
<td>写入 int 值</td>
<td>Little Endian（小端写入），即 0x250，写入后 50 02 00 00</td>
</tr>
<tr>
<td>writeLong(long value)</td>
<td>写入 long 值</td>
<td></td>
</tr>
<tr>
<td>writeChar(int value)</td>
<td>写入 char 值</td>
<td></td>
</tr>
<tr>
<td>writeFloat(float value)</td>
<td>写入 float 值</td>
<td></td>
</tr>
<tr>
<td>writeDouble(double value)</td>
<td>写入 double 值</td>
<td></td>
</tr>
<tr>
<td>writeBytes(ByteBuf src)</td>
<td>写入 netty 的 ByteBuf</td>
<td></td>
</tr>
<tr>
<td>writeBytes(byte[] src)</td>
<td>写入 byte[]</td>
<td></td>
</tr>
<tr>
<td>writeBytes(ByteBuffer src)</td>
<td>写入 nio 的 <strong>ByteBuffer</strong></td>
<td></td>
</tr>
<tr>
<td>int writeCharSequence(CharSequence sequence, Charset charset)</td>
<td>写入字符串</td>
<td>CharSequence为字符串类的父类，第二个参数为对应的字符集</td>
</tr>
</tbody></table>
<blockquote>
<p>注意</p>
<ul>
<li>这些方法的未指明返回值的，其返回值都是 ByteBuf，意味着可以链式调用来写入不同的数据</li>
<li>网络传输中，<strong>默认习惯是 Big Endian</strong>，使用 writeInt(int value)</li>
</ul>
</blockquote>
<p><strong>使用方法</strong></p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs arduino"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ByteBufStudy</span> &#123;</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">String</span>[] args)</span> </span>&#123;<br>        <span class="hljs-comment">// 创建ByteBuf</span><br>        ByteBuf buffer = ByteBufAllocator.<span class="hljs-literal">DEFAULT</span>.<span class="hljs-built_in">buffer</span>(<span class="hljs-number">16</span>, <span class="hljs-number">20</span>);<br>        ByteBufUtil.<span class="hljs-built_in">log</span>(buffer);<br><br>        <span class="hljs-comment">// 向buffer中写入数据</span><br>        buffer.<span class="hljs-built_in">writeBytes</span>(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]&#123;<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>&#125;);<br>        ByteBufUtil.<span class="hljs-built_in">log</span>(buffer);<br><br>        buffer.<span class="hljs-built_in">writeInt</span>(<span class="hljs-number">5</span>);<br>        ByteBufUtil.<span class="hljs-built_in">log</span>(buffer);<br><br>        buffer.<span class="hljs-built_in">writeIntLE</span>(<span class="hljs-number">6</span>);<br>        ByteBufUtil.<span class="hljs-built_in">log</span>(buffer);<br><br>        buffer.<span class="hljs-built_in">writeLong</span>(<span class="hljs-number">7</span>);<br>        ByteBufUtil.<span class="hljs-built_in">log</span>(buffer);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>运行结果</strong></p>
<figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs asciidoc">read index:0 write index:0 capacity:16<br><br>read index:0 write index:4 capacity:16<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| 01 02 03 04                                     |....            |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><br>read index:0 write index:8 capacity:16<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| 01 02 03 04 00 00 00 05                         |........        |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><br>read index:0 write index:12 capacity:16<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| 01 02 03 04 00 00 00 05 06 00 00 00             |............    |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><br>read index:0 write index:20 capacity:20<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br>|00000000| 01 02 03 04 00 00 00 05 06 00 00 00 00 00 00 00 |................|<br>|00000010| 00 00 00 07                                     |....            |<br><span class="hljs-code">+--------+</span>-------------------------------------------------<span class="hljs-code">+----------------+</span> <br></code></pre></td></tr></table></figure>

<p>还有一类方法是 <strong>set 开头</strong>的一系列方法，也<strong>可以写入数据，但不会改变写指针位置</strong></p>
<h3 id="扩容"><a href="#扩容" class="headerlink" title="扩容"></a>扩容</h3><p>当ByteBuf中的容量无法容纳写入的数据时，会进行扩容操作</p>
<figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs tap">buffer.writeLong(7);<br>ByteBufUtil.log(buffer);Copy<br>// 扩容前<br>read index:0 write index:12 capacity:16<br>...<br><br>// 扩容后<br>read index:0 write index:20 capacity:20<br>         +-------------------------------------------------+<br>         | <span class="hljs-number"> 0 </span><span class="hljs-number"> 1 </span><span class="hljs-number"> 2 </span><span class="hljs-number"> 3 </span><span class="hljs-number"> 4 </span><span class="hljs-number"> 5 </span><span class="hljs-number"> 6 </span><span class="hljs-number"> 7 </span><span class="hljs-number"> 8 </span><span class="hljs-number"> 9 </span> a  b  c  d  e  f |<br>+--------+-------------------------------------------------+----------------+<br>|00000000|<span class="hljs-number"> 01 </span>02<span class="hljs-number"> 03 </span>04<span class="hljs-number"> 00 </span>00<span class="hljs-number"> 00 </span>05<span class="hljs-number"> 06 </span>00<span class="hljs-number"> 00 </span>00<span class="hljs-number"> 00 </span>00<span class="hljs-number"> 00 </span>00 |................|<br>|00000010|<span class="hljs-number"> 00 </span>00<span class="hljs-number"> 00 </span>07                                     |....            |<br>+--------+-------------------------------------------------+----------------+ <br></code></pre></td></tr></table></figure>

<h4 id="扩容规则"><a href="#扩容规则" class="headerlink" title="扩容规则"></a>扩容规则</h4><ul>
<li><p>如何写入后数据大小未超过 512 字节，则选择下一个 16 的整数倍进行扩容</p>
<ul>
<li>例如写入后大小为 12 字节，则扩容后 capacity 是 16 字节</li>
</ul>
</li>
<li><p>如果写入后数据大小超过 512 字节，则选择下一个 2</p>
<p>n</p>
<ul>
<li>例如写入后大小为 513 字节，则扩容后 capacity 是 210=1024 字节（29=512 已经不够了）</li>
</ul>
</li>
<li><p>扩容<strong>不能超过</strong> maxCapacity，否则会抛出<code>java.lang.IndexOutOfBoundsException</code>异常</p>
</li>
</ul>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">Exception <span class="hljs-keyword">in</span> thread <span class="hljs-string">&quot;main&quot;</span> java.lang.IndexOutOfBoundsException: writer<span class="hljs-constructor">Index(20)</span> + min<span class="hljs-constructor">WritableBytes(8)</span> exceeds max<span class="hljs-constructor">Capacity(20)</span>: <span class="hljs-constructor">PooledUnsafeDirectByteBuf(<span class="hljs-params">ridx</span>: 0, <span class="hljs-params">widx</span>: 20, <span class="hljs-params">cap</span>: 20<span class="hljs-operator">/</span>20)</span><span class="hljs-operator"></span><br><span class="hljs-operator">... </span><br></code></pre></td></tr></table></figure>

<h3 id="读取"><a href="#读取" class="headerlink" title="读取"></a>读取</h3><p>读取主要是通过一系列read方法进行读取，读取时会根据读取数据的字节数移动读指针</p>
<p>如果需要<strong>重复读取</strong>，需要调用<code>buffer.markReaderIndex()</code>对读指针进行标记，并通过<code>buffer.resetReaderIndex()</code>将读指针恢复到mark标记的位置</p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><code class="hljs arduino"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ByteBufStudy</span> &#123;</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">String</span>[] args)</span> </span>&#123;<br>        <span class="hljs-comment">// 创建ByteBuf</span><br>        ByteBuf buffer = ByteBufAllocator.<span class="hljs-literal">DEFAULT</span>.<span class="hljs-built_in">buffer</span>(<span class="hljs-number">16</span>, <span class="hljs-number">20</span>);<br><br>        <span class="hljs-comment">// 向buffer中写入数据</span><br>        buffer.<span class="hljs-built_in">writeBytes</span>(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]&#123;<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>&#125;);<br>        buffer.<span class="hljs-built_in">writeInt</span>(<span class="hljs-number">5</span>);<br><br>        <span class="hljs-comment">// 读取4个字节</span><br>        System.out.<span class="hljs-built_in">println</span>(buffer.<span class="hljs-built_in">readByte</span>());<br>        System.out.<span class="hljs-built_in">println</span>(buffer.<span class="hljs-built_in">readByte</span>());<br>        System.out.<span class="hljs-built_in">println</span>(buffer.<span class="hljs-built_in">readByte</span>());<br>        System.out.<span class="hljs-built_in">println</span>(buffer.<span class="hljs-built_in">readByte</span>());<br>        ByteBufUtil.<span class="hljs-built_in">log</span>(buffer);<br><br>        <span class="hljs-comment">// 通过mark与reset实现重复读取</span><br>        buffer.<span class="hljs-built_in">markReaderIndex</span>();<br>        System.out.<span class="hljs-built_in">println</span>(buffer.<span class="hljs-built_in">readInt</span>());<br>        ByteBufUtil.<span class="hljs-built_in">log</span>(buffer);<br><br>        <span class="hljs-comment">// 恢复到mark标记处</span><br>        buffer.<span class="hljs-built_in">resetReaderIndex</span>();<br>        ByteBufUtil.<span class="hljs-built_in">log</span>(buffer);<br>    &#125;<br>&#125; <br><span class="hljs-number">1</span><br><span class="hljs-number">2</span><br><span class="hljs-number">3</span><br><span class="hljs-number">4</span><br>read index:<span class="hljs-number">4</span> write index:<span class="hljs-number">8</span> capacity:<span class="hljs-number">16</span><br>         +-------------------------------------------------+<br>         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |<br>+--------+-------------------------------------------------+----------------+<br>|<span class="hljs-number">00000000</span>| <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">05</span>                                     |....            |<br>+--------+-------------------------------------------------+----------------+<br><span class="hljs-number">5</span><br>read index:<span class="hljs-number">8</span> write index:<span class="hljs-number">8</span> capacity:<span class="hljs-number">16</span><br><br>read index:<span class="hljs-number">4</span> write index:<span class="hljs-number">8</span> capacity:<span class="hljs-number">16</span><br>         +-------------------------------------------------+<br>         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |<br>+--------+-------------------------------------------------+----------------+<br>|<span class="hljs-number">00000000</span>| <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">05</span>                                     |....            |<br>+--------+-------------------------------------------------+----------------+ <br></code></pre></td></tr></table></figure>

<p>还有以 get 开头的一系列方法，这些<strong>方法不会改变读指针的位置</strong></p>
<h3 id="释放"><a href="#释放" class="headerlink" title="释放"></a>释放</h3><p>由于 Netty 中有堆外内存（直接内存）的 ByteBuf 实现，<strong>堆外内存最好是手动来释放</strong>，而不是等 GC 垃圾回收。</p>
<ul>
<li>UnpooledHeapByteBuf 使用的是 JVM 内存，只需等 GC 回收内存即可</li>
<li>UnpooledDirectByteBuf 使用的就是直接内存了，需要特殊的方法来回收内存</li>
<li>PooledByteBuf 和它的子类使用了池化机制，需要更复杂的规则来回收内存</li>
</ul>
<p>Netty 这里采用了引用计数法来控制回收内存，每个 ByteBuf 都实现了 ReferenceCounted 接口</p>
<ul>
<li>每个 ByteBuf 对象的初始计数为 1</li>
<li>调用 release 方法计数减 1，如果计数为 0，ByteBuf 内存被回收</li>
<li>调用 retain 方法计数加 1，表示调用者没用完之前，其它 handler 即使调用了 release 也不会造成回收</li>
<li>当计数为 0 时，底层内存会被回收，这时即使 ByteBuf 对象还在，其各个方法均无法正常使用</li>
</ul>
<h4 id="释放规则"><a href="#释放规则" class="headerlink" title="释放规则"></a>释放规则</h4><p>因为 pipeline 的存在，一般需要将 ByteBuf 传递给下一个 ChannelHandler，如果在每个 ChannelHandler 中都去调用 release ，就失去了传递性（如果在这个 ChannelHandler 内这个 ByteBuf 已完成了它的使命，那么便无须再传递）</p>
<p><strong>基本规则是，谁是最后使用者，谁负责 release</strong></p>
<ul>
<li><p>起点，对于 NIO 实现来讲，在 io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe.read 方法中首次创建 ByteBuf 放入 pipeline（line 163 pipeline.fireChannelRead(byteBuf)）</p>
</li>
<li><p>入站 ByteBuf 处理原则</p>
<ul>
<li>对原始 ByteBuf 不做处理，调用 ctx.fireChannelRead(msg) 向后传递，这时无须 release</li>
<li><strong>将原始 ByteBuf 转换为其它类型的 Java 对象，这时 ByteBuf 就没用了，必须 release</strong></li>
<li><strong>如果不调用 ctx.fireChannelRead(msg) 向后传递，那么也必须 release</strong></li>
<li><strong>注意各种异常，如果 ByteBuf 没有成功传递到下一个 ChannelHandler，必须 release</strong></li>
<li>假设消息<strong>一直向后传</strong>，那么 TailContext 会负责释放未处理消息（原始的 ByteBuf）</li>
</ul>
</li>
<li><p>出站 ByteBuf 处理原则</p>
<ul>
<li><strong>出站消息最终都会转为 ByteBuf 输出，一直向前传，由 HeadContext flush 后 release</strong></li>
</ul>
</li>
<li><p>异常处理原则</p>
<ul>
<li><p>有时候不清楚 ByteBuf 被引用了多少次，但又必须彻底释放，可以<strong>循环调用 release 直到返回 true</strong></p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs arduino"><span class="hljs-keyword">while</span> (!buffer.<span class="hljs-built_in">release</span>()) &#123;&#125; <br></code></pre></td></tr></table></figure></li>
</ul>
</li>
</ul>
<p>当ByteBuf<strong>被传到了pipeline的head与tail时</strong>，ByteBuf会被其中的方法彻底释放，但<strong>前提是ByteBuf被传递到了head与tail中</strong></p>
<p><strong>TailConext中释放ByteBuf的源码</strong></p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">protected</span> <span class="hljs-built_in">void</span> <span class="hljs-function"><span class="hljs-title">onUnhandledInboundMessage</span>(<span class="hljs-params"><span class="hljs-built_in">Object</span> msg</span>)</span> &#123;<br>    <span class="hljs-keyword">try</span> &#123;<br>        logger.debug(<span class="hljs-string">&quot;Discarded inbound message &#123;&#125; that reached at the tail of the pipeline. Please check your pipeline configuration.&quot;</span>, msg);<br>    &#125; <span class="hljs-keyword">finally</span> &#123;<br>        <span class="hljs-comment">// 具体的释放方法</span><br>        ReferenceCountUtil.release(msg);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>判断传过来的是否为ByteBuf，是的话才需要释放</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-built_in">boolean</span> <span class="hljs-function"><span class="hljs-title">release</span>(<span class="hljs-params"><span class="hljs-built_in">Object</span> msg</span>)</span> &#123;<br>	<span class="hljs-keyword">return</span> msg <span class="hljs-keyword">instanceof</span> ReferenceCounted ? ((ReferenceCounted)msg).release() : <span class="hljs-literal">false</span>;<br>&#125; <br></code></pre></td></tr></table></figure>

<h3 id="切片"><a href="#切片" class="headerlink" title="切片"></a>切片</h3><p>ByteBuf切片是【零拷贝】的体现之一，对原始 ByteBuf 进行切片成多个 ByteBuf，<strong>切片后的 ByteBuf 并没有发生内存复制，还是使用原始 ByteBuf 的内存</strong>，切片后的 ByteBuf 维护独立的 read，write 指针</p>
<p>得到分片后的buffer后，要调用其retain方法，使其内部的引用计数加一。避免原ByteBuf释放，导致切片buffer无法使用</p>
<p>修改原ByteBuf中的值，也会影响切片后得到的ByteBuf</p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115739.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs arduino"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestSlice</span> &#123;</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">String</span>[] args)</span> </span>&#123;<br>        <span class="hljs-comment">// 创建ByteBuf</span><br>        ByteBuf buffer = ByteBufAllocator.<span class="hljs-literal">DEFAULT</span>.<span class="hljs-built_in">buffer</span>(<span class="hljs-number">16</span>, <span class="hljs-number">20</span>);<br><br>        <span class="hljs-comment">// 向buffer中写入数据</span><br>        buffer.<span class="hljs-built_in">writeBytes</span>(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]&#123;<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>, <span class="hljs-number">9</span>, <span class="hljs-number">10</span>&#125;);<br><br>        <span class="hljs-comment">// 将buffer分成两部分</span><br>        ByteBuf slice1 = buffer.<span class="hljs-built_in">slice</span>(<span class="hljs-number">0</span>, <span class="hljs-number">5</span>);<br>        ByteBuf slice2 = buffer.<span class="hljs-built_in">slice</span>(<span class="hljs-number">5</span>, <span class="hljs-number">5</span>);<br><br>        <span class="hljs-comment">// 需要让分片的buffer引用计数加一</span><br>        <span class="hljs-comment">// 避免原Buffer释放导致分片buffer无法使用</span><br>        slice1.<span class="hljs-built_in">retain</span>();<br>        slice2.<span class="hljs-built_in">retain</span>();<br>        <br>        ByteBufUtil.<span class="hljs-built_in">log</span>(slice1);<br>        ByteBufUtil.<span class="hljs-built_in">log</span>(slice2);<br><br>        <span class="hljs-comment">// 更改原始buffer中的值</span><br>        System.out.<span class="hljs-built_in">println</span>(<span class="hljs-string">&quot;===========修改原buffer中的值===========&quot;</span>);<br>        buffer.<span class="hljs-built_in">setByte</span>(<span class="hljs-number">0</span>,<span class="hljs-number">5</span>);<br><br>        System.out.<span class="hljs-built_in">println</span>(<span class="hljs-string">&quot;===========打印slice1===========&quot;</span>);<br>        ByteBufUtil.<span class="hljs-built_in">log</span>(slice1);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>运行结果</p>
<figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs asciidoc">read index:0 write index:5 capacity:5<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| 01 02 03 04 05                                  |.....           |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br>read index:0 write index:5 capacity:5<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| 06 07 08 09 0a                                  |.....           |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br>===========修改原buffer中的值===========<br>===========打印slice1===========<br>read index:0 write index:5 capacity:5<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br>|00000000| 05 02 03 04 05                                  |.....           |<br><span class="hljs-code">+--------+</span>-------------------------------------------------<span class="hljs-code">+----------------+</span> <br></code></pre></td></tr></table></figure>

<h3 id="优势"><a href="#优势" class="headerlink" title="优势"></a>优势</h3><ul>
<li>池化思想 - 可以重用池中 ByteBuf 实例，更节约内存，减少内存溢出的可能</li>
<li><strong>读写指针分离</strong>，不需要像 ByteBuffer 一样切换读写模式</li>
<li>可以<strong>自动扩容</strong></li>
<li>支持链式调用，使用更流畅</li>
<li>很多地方体现零拷贝，例如<ul>
<li>slice、duplicate、CompositeByteBuf</li>
</ul>
</li>
</ul>
<h1 id="四、应用"><a href="#四、应用" class="headerlink" title="四、应用"></a>四、应用</h1><h2 id="1、粘包与半包"><a href="#1、粘包与半包" class="headerlink" title="1、粘包与半包"></a>1、粘包与半包</h2><h3 id="服务器代码-1"><a href="#服务器代码-1" class="headerlink" title="服务器代码"></a>服务器代码</h3><figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><code class="hljs aspectj"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StudyServer</span> </span>&#123;<br>    <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Logger log = LoggerFactory.getLogger(StudyServer.class);<br>    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">start</span><span class="hljs-params">()</span> </span>&#123;<br>        NioEventLoopGroup boss = <span class="hljs-keyword">new</span> NioEventLoopGroup(<span class="hljs-number">1</span>);<br>        NioEventLoopGroup worker = <span class="hljs-keyword">new</span> NioEventLoopGroup();<br>        <span class="hljs-keyword">try</span> &#123;<br>            ServerBootstrap serverBootstrap = <span class="hljs-keyword">new</span> ServerBootstrap();<br>            serverBootstrap.channel(NioServerSocketChannel.class);<br>            serverBootstrap.group(boss, worker);<br>            serverBootstrap.childHandler(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;() &#123;<br>                <span class="hljs-meta">@Override</span><br>                <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel ch)</span> </span>&#123;<br>                    ch.pipeline().addLast(<span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG));<br>                    ch.pipeline().addLast(<span class="hljs-keyword">new</span> ChannelInboundHandlerAdapter() &#123;<br>                        <span class="hljs-meta">@Override</span><br>                        <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">channelActive</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;<br>                            <span class="hljs-comment">// 连接建立时会执行该方法</span><br>                            log.debug(<span class="hljs-string">&quot;connected &#123;&#125;&quot;</span>, ctx.channel());<br>                            <span class="hljs-keyword">super</span>.channelActive(ctx);<br>                        &#125;<br><br>                        <span class="hljs-meta">@Override</span><br>                        <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">channelInactive</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;<br>                            <span class="hljs-comment">// 连接断开时会执行该方法</span><br>                            log.debug(<span class="hljs-string">&quot;disconnect &#123;&#125;&quot;</span>, ctx.channel());<br>                            <span class="hljs-keyword">super</span>.channelInactive(ctx);<br>                        &#125;<br>                    &#125;);<br>                &#125;<br>            &#125;);<br>            ChannelFuture channelFuture = serverBootstrap.bind(<span class="hljs-number">8080</span>);<br>            log.debug(<span class="hljs-string">&quot;&#123;&#125; binding...&quot;</span>, channelFuture.channel());<br>            channelFuture.sync();<br>            log.debug(<span class="hljs-string">&quot;&#123;&#125; bound...&quot;</span>, channelFuture.channel());<br>            <span class="hljs-comment">// 关闭channel</span><br>            channelFuture.channel().closeFuture().sync();<br>        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>            log.<span class="hljs-keyword">error</span>(<span class="hljs-string">&quot;server error&quot;</span>, e);<br>        &#125; <span class="hljs-keyword">finally</span> &#123;<br>            boss.shutdownGracefully();<br>            worker.shutdownGracefully();<br>            log.debug(<span class="hljs-string">&quot;stopped&quot;</span>);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;<br>        <span class="hljs-keyword">new</span> StudyServer().start();<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h3 id="粘包现象"><a href="#粘包现象" class="headerlink" title="粘包现象"></a>粘包现象</h3><p><strong>客户端代码</strong></p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs arduino"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StudyClient</span> &#123;</span><br>    <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Logger log = LoggerFactory.<span class="hljs-built_in">getLogger</span>(StudyClient.class);<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">String</span>[] args)</span> </span>&#123;<br>        NioEventLoopGroup worker = <span class="hljs-keyword">new</span> <span class="hljs-built_in">NioEventLoopGroup</span>();<br>        <span class="hljs-keyword">try</span> &#123;<br>            Bootstrap bootstrap = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Bootstrap</span>();<br>            bootstrap.<span class="hljs-built_in">channel</span>(NioSocketChannel.class);<br>            bootstrap.<span class="hljs-built_in">group</span>(worker);<br>            bootstrap.<span class="hljs-built_in">handler</span>(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;() &#123;<br>                @Override<br>                <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-built_in">initChannel</span>(SocketChannel ch) throws Exception &#123;<br>                    log.<span class="hljs-built_in">debug</span>(<span class="hljs-string">&quot;connected...&quot;</span>);<br>                    ch.<span class="hljs-built_in">pipeline</span>().<span class="hljs-built_in">addLast</span>(<span class="hljs-keyword">new</span> <span class="hljs-built_in">ChannelInboundHandlerAdapter</span>() &#123;<br>                        @Override<br>                        <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-built_in">channelActive</span>(ChannelHandlerContext ctx) throws Exception &#123;<br>                            log.<span class="hljs-built_in">debug</span>(<span class="hljs-string">&quot;sending...&quot;</span>);<br>                            <span class="hljs-comment">// 每次发送16个字节的数据，共发送10次</span><br>                            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) &#123;<br>                                ByteBuf buffer = ctx.<span class="hljs-built_in">alloc</span>().<span class="hljs-built_in">buffer</span>();<br>                                buffer.<span class="hljs-built_in">writeBytes</span>(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]&#123;<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>, <span class="hljs-number">9</span>, <span class="hljs-number">10</span>, <span class="hljs-number">11</span>, <span class="hljs-number">12</span>, <span class="hljs-number">13</span>, <span class="hljs-number">14</span>, <span class="hljs-number">15</span>&#125;);<br>                                ctx.<span class="hljs-built_in">writeAndFlush</span>(buffer);<br>                            &#125;<br>                        &#125;<br>                    &#125;);<br>                &#125;<br>            &#125;);<br>            ChannelFuture channelFuture = bootstrap.<span class="hljs-built_in">connect</span>(<span class="hljs-string">&quot;127.0.0.1&quot;</span>, <span class="hljs-number">8080</span>).<span class="hljs-built_in">sync</span>();<br>            channelFuture.<span class="hljs-built_in">channel</span>().<span class="hljs-built_in">closeFuture</span>().<span class="hljs-built_in">sync</span>();<br><br>        &#125; <span class="hljs-built_in"><span class="hljs-keyword">catch</span></span> (InterruptedException e) &#123;<br>            log.<span class="hljs-built_in">error</span>(<span class="hljs-string">&quot;client error&quot;</span>, e);<br>        &#125; finally &#123;<br>            worker.<span class="hljs-built_in">shutdownGracefully</span>();<br>        &#125;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>服务器接收结果</strong></p>
<figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs tap">7999 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x5b43ecb0, L:/127.0.0.1:8080 - R:/127.0.0.1:53797] READ: 160B<br>         +-------------------------------------------------+<br>         | <span class="hljs-number"> 0 </span><span class="hljs-number"> 1 </span><span class="hljs-number"> 2 </span><span class="hljs-number"> 3 </span><span class="hljs-number"> 4 </span><span class="hljs-number"> 5 </span><span class="hljs-number"> 6 </span><span class="hljs-number"> 7 </span><span class="hljs-number"> 8 </span><span class="hljs-number"> 9 </span> a  b  c  d  e  f |<br>+--------+-------------------------------------------------+----------------+<br>|00000000|<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f |................|<br>|00000010|<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f |................|<br>|00000020|<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f |................|<br>|00000030|<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f |................|<br>|00000040|<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f |................|<br>|00000050|<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f |................|<br>|00000060|<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f |................|<br>|00000070|<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f |................|<br>|00000080|<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f |................|<br>|00000090|<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f |................|<br>+--------+-------------------------------------------------+----------------+ <br></code></pre></td></tr></table></figure>

<p>可见虽然客户端是分别以16字节为单位，通过channel向服务器发送了10次数据，可是<strong>服务器端却只接收了一次，接收数据的大小为160B，即客户端发送的数据总大小，这就是粘包现象</strong></p>
<h3 id="半包现象"><a href="#半包现象" class="headerlink" title="半包现象"></a>半包现象</h3><p>将客户端-服务器之间的channel容量进行调整</p>
<p><strong>服务器代码</strong></p>
<figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs awk"><span class="hljs-regexp">//</span> 调整channel的容量<br>serverBootstrap.option(ChannelOption.SO_RCVBUF, <span class="hljs-number">10</span>);Copy<br></code></pre></td></tr></table></figure>

<p><strong>注意</strong></p>
<blockquote>
<p>serverBootstrap.option(ChannelOption.SO_RCVBUF, 10) 影响的底层接收缓冲区（即滑动窗口）大小，仅决定了 netty 读取的最小单位，<strong>netty 实际每次读取的一般是它的整数倍</strong></p>
</blockquote>
<p><strong>服务器接收结果</strong></p>
<figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><code class="hljs tap">5901 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xc73284f3, L:/127.0.0.1:8080 - R:/127.0.0.1:49679] READ: 36B<br>         +-------------------------------------------------+<br>         | <span class="hljs-number"> 0 </span><span class="hljs-number"> 1 </span><span class="hljs-number"> 2 </span><span class="hljs-number"> 3 </span><span class="hljs-number"> 4 </span><span class="hljs-number"> 5 </span><span class="hljs-number"> 6 </span><span class="hljs-number"> 7 </span><span class="hljs-number"> 8 </span><span class="hljs-number"> 9 </span> a  b  c  d  e  f |<br>+--------+-------------------------------------------------+----------------+<br>|00000000|<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f |................|<br>|00000010|<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f |................|<br>|00000020|<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03                                     |....            |<br>+--------+-------------------------------------------------+----------------+<br><br>5901 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xc73284f3, L:/127.0.0.1:8080 - R:/127.0.0.1:49679] READ: 40B<br>         +-------------------------------------------------+<br>         | <span class="hljs-number"> 0 </span><span class="hljs-number"> 1 </span><span class="hljs-number"> 2 </span><span class="hljs-number"> 3 </span><span class="hljs-number"> 4 </span><span class="hljs-number"> 5 </span><span class="hljs-number"> 6 </span><span class="hljs-number"> 7 </span><span class="hljs-number"> 8 </span><span class="hljs-number"> 9 </span> a  b  c  d  e  f |<br>+--------+-------------------------------------------------+----------------+<br>|00000000|<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03 |................|<br>|00000010|<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03 |................|<br>|00000020|<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b                         |........        |<br>+--------+-------------------------------------------------+----------------+<br><br>5901 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xc73284f3, L:/127.0.0.1:8080 - R:/127.0.0.1:49679] READ: 40B<br>         +-------------------------------------------------+<br>         | <span class="hljs-number"> 0 </span><span class="hljs-number"> 1 </span><span class="hljs-number"> 2 </span><span class="hljs-number"> 3 </span><span class="hljs-number"> 4 </span><span class="hljs-number"> 5 </span><span class="hljs-number"> 6 </span><span class="hljs-number"> 7 </span><span class="hljs-number"> 8 </span><span class="hljs-number"> 9 </span> a  b  c  d  e  f |<br>+--------+-------------------------------------------------+----------------+<br>|00000000| 0c 0d 0e 0f<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b |................|<br>|00000010| 0c 0d 0e 0f<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b |................|<br>|00000020| 0c 0d 0e 0f<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03                         |........        |<br>+--------+-------------------------------------------------+----------------+<br><br>5901 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xc73284f3, L:/127.0.0.1:8080 - R:/127.0.0.1:49679] READ: 40B<br>         +-------------------------------------------------+<br>         | <span class="hljs-number"> 0 </span><span class="hljs-number"> 1 </span><span class="hljs-number"> 2 </span><span class="hljs-number"> 3 </span><span class="hljs-number"> 4 </span><span class="hljs-number"> 5 </span><span class="hljs-number"> 6 </span><span class="hljs-number"> 7 </span><span class="hljs-number"> 8 </span><span class="hljs-number"> 9 </span> a  b  c  d  e  f |<br>+--------+-------------------------------------------------+----------------+<br>|00000000|<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03 |................|<br>|00000010|<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b 0c 0d 0e 0f<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 02 </span>03 |................|<br>|00000020|<span class="hljs-number"> 04 </span>05<span class="hljs-number"> 06 </span>07<span class="hljs-number"> 08 </span>09 0a 0b                         |........        |<br>+--------+-------------------------------------------------+----------------+<br><br>5901 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xc73284f3, L:/127.0.0.1:8080 - R:/127.0.0.1:49679] READ: 4B<br>         +-------------------------------------------------+<br>         | <span class="hljs-number"> 0 </span><span class="hljs-number"> 1 </span><span class="hljs-number"> 2 </span><span class="hljs-number"> 3 </span><span class="hljs-number"> 4 </span><span class="hljs-number"> 5 </span><span class="hljs-number"> 6 </span><span class="hljs-number"> 7 </span><span class="hljs-number"> 8 </span><span class="hljs-number"> 9 </span> a  b  c  d  e  f |<br>+--------+-------------------------------------------------+----------------+<br>|00000000| 0c 0d 0e 0f                                     |....            |<br>+--------+-------------------------------------------------+----------------+ <br></code></pre></td></tr></table></figure>

<p>可见客户端每次发送的数据，<strong>因channel容量不足，无法将发送的数据一次性接收</strong>，便产生了半包现象</p>
<h3 id="现象分析"><a href="#现象分析" class="headerlink" title="现象分析"></a>现象分析</h3><h4 id="粘包"><a href="#粘包" class="headerlink" title="粘包"></a>粘包</h4><ul>
<li>现象<ul>
<li>发送 abc def，接收 abcdef</li>
</ul>
</li>
<li>原因<ul>
<li>应用层<ul>
<li>接收方 ByteBuf 设置太大（Netty 默认 1024）</li>
</ul>
</li>
<li>传输层-网络层<ul>
<li>滑动窗口：假设发送方 256 bytes 表示一个完整报文，但由于接收方处理不及时且<strong>窗口大小足够大（大于256 bytes），这 256 bytes 字节就会缓冲在接收方的滑动窗口中，</strong>当滑动窗口中缓冲了多个报文就会粘包</li>
<li>Nagle 算法：会造成粘包</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="半包"><a href="#半包" class="headerlink" title="半包"></a>半包</h4><ul>
<li>现象<ul>
<li>发送 abcdef，接收 abc def</li>
</ul>
</li>
<li>原因<ul>
<li>应用层<ul>
<li>接收方 ByteBuf 小于实际发送数据量</li>
</ul>
</li>
<li>传输层-网络层<ul>
<li>滑动窗口：假设接收方的窗口只剩了 128 bytes，发送方的报文大小是 256 bytes，这时<strong>接收方窗口中无法容纳发送方的全部报文，发送方只能先发送前 128 bytes，等待 ack 后才能发送剩余部分，这就造成了半包</strong></li>
</ul>
</li>
<li>数据链路层<ul>
<li>MSS 限制：当发送的数据超过 MSS 限制后，会将数据切分发送，就会造成半包</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="本质"><a href="#本质" class="headerlink" title="本质"></a>本质</h4><p>发生粘包与半包现象的本质是<strong>因为 TCP 是流式协议，消息无边界</strong></p>
<h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><h4 id="短链接"><a href="#短链接" class="headerlink" title="短链接"></a>短链接</h4><p><strong>客户端每次向服务器发送数据以后，就与服务器断开连接，此时的消息边界为连接建立到连接断开</strong>。这时便无需使用滑动窗口等技术来缓冲数据，则不会发生粘包现象。但如果一次性数据发送过多，接收方无法一次性容纳所有数据，还是会发生半包现象，所以<strong>短链接无法解决半包现象</strong></p>
<p><strong>客户端代码改进</strong></p>
<p>修改channelActive方法</p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs arduino"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelActive</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> throws Exception </span>&#123;<br>    log.<span class="hljs-built_in">debug</span>(<span class="hljs-string">&quot;sending...&quot;</span>);<br>    ByteBuf buffer = ctx.<span class="hljs-built_in">alloc</span>().<span class="hljs-built_in">buffer</span>(<span class="hljs-number">16</span>);<br>    buffer.<span class="hljs-built_in">writeBytes</span>(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]&#123;<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>, <span class="hljs-number">9</span>, <span class="hljs-number">10</span>, <span class="hljs-number">11</span>, <span class="hljs-number">12</span>, <span class="hljs-number">13</span>, <span class="hljs-number">14</span>, <span class="hljs-number">15</span>&#125;);<br>    ctx.<span class="hljs-built_in">writeAndFlush</span>(buffer);<br>    <span class="hljs-comment">// 使用短链接，每次发送完毕后就断开连接</span><br>    ctx.<span class="hljs-built_in">channel</span>().<span class="hljs-built_in">close</span>();<br>&#125; <br></code></pre></td></tr></table></figure>

<p>将发送步骤整体封装为send()方法，调用10次send()方法，模拟发送10次数据</p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs arduino"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">String</span>[] args)</span> </span>&#123;<br>    <span class="hljs-comment">// 发送10次</span><br>    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) &#123;<br>        <span class="hljs-built_in">send</span>();<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>运行结果</strong></p>
<figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs asciidoc">6452 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x3eb6a684, L:/127.0.0.1:8080 - R:/127.0.0.1:65024] ACTIVE<br><br>6468 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x3eb6a684, L:/127.0.0.1:8080 - R:/127.0.0.1:65024] READ: 16B<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><br>6468 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x3eb6a684, L:/127.0.0.1:8080 ! R:/127.0.0.1:65024] INACTIVE<br><br>6483 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x7dcc31ff, L:/127.0.0.1:8080 - R:/127.0.0.1:65057] ACTIVE<br><br>6483 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x7dcc31ff, L:/127.0.0.1:8080 - R:/127.0.0.1:65057] READ: 16B<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><br>6483 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x7dcc31ff, L:/127.0.0.1:8080 ! R:/127.0.0.1:65057] INACTIVE<br><br><span class="hljs-bullet">... </span><br></code></pre></td></tr></table></figure>

<p>客户端先于服务器建立连接，此时控制台打印<code>ACTIVE</code>，之后客户端向服务器发送了16B的数据，发送后断开连接，此时控制台打印<code>INACTIVE</code>，可见<strong>未出现粘包现象</strong></p>
<h4 id="定长解码器"><a href="#定长解码器" class="headerlink" title="定长解码器"></a>定长解码器</h4><p>客户端于服务器<strong>约定一个最大长度，保证客户端每次发送的数据长度都不会大于该长度</strong>。若发送数据长度不足则需要<strong>补齐</strong>至该长度</p>
<p>服务器接收数据时，<strong>将接收到的数据按照约定的最大长度进行拆分</strong>，即使发送过程中产生了粘包，也可以通过定长解码器将数据正确地进行拆分。<strong>服务端需要用到<code>FixedLengthFrameDecoder</code>对数据进行定长解码</strong>，具体使用方法如下</p>
<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs gradle">ch.pipeline().addLast(<span class="hljs-keyword">new</span> FixedLengthFrameDecoder(<span class="hljs-number">16</span>));<span class="hljs-keyword">Copy</span><br></code></pre></td></tr></table></figure>

<p><strong>客户端代码</strong></p>
<p>客户端发送数据的代码如下</p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs arduino"><span class="hljs-comment">// 约定最大长度为16</span><br><span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> maxLength = <span class="hljs-number">16</span>;<br><span class="hljs-comment">// 被发送的数据</span><br><span class="hljs-keyword">char</span> c = <span class="hljs-string">&#x27;a&#x27;</span>;<br><span class="hljs-comment">// 向服务器发送10个报文</span><br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) &#123;<br>    ByteBuf buffer = ctx.<span class="hljs-built_in">alloc</span>().<span class="hljs-built_in">buffer</span>(maxLength);<br>    <span class="hljs-comment">// 定长byte数组，未使用部分会以0进行填充</span><br>    <span class="hljs-keyword">byte</span>[] bytes = <span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[maxLength];<br>    <span class="hljs-comment">// 生成长度为0~15的数据</span><br>    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>; j &lt; (<span class="hljs-keyword">int</span>)(Math.<span class="hljs-built_in">random</span>()*(maxLength<span class="hljs-number">-1</span>)); j++) &#123;<br>        bytes[j] = (<span class="hljs-keyword">byte</span>) c;<br>    &#125;<br>    buffer.<span class="hljs-built_in">writeBytes</span>(bytes);<br>    c++;<br>    <span class="hljs-comment">// 将数据发送给服务器</span><br>    ctx.<span class="hljs-built_in">writeAndFlush</span>(buffer);<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>服务器代码</strong></p>
<p>使用<code>FixedLengthFrameDecoder</code>对粘包数据进行拆分，该handler需要添加在<code>LoggingHandler</code>之前，保证数据被打印时已被拆分</p>
<figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs haxe"><span class="hljs-comment">// 通过定长解码器对粘包数据进行拆分</span><br>ch.pipeline().addLast(<span class="hljs-keyword">new</span> <span class="hljs-type">FixedLengthFrameDecoder</span>(<span class="hljs-number">16</span>));<br>ch.pipeline().addLast(<span class="hljs-keyword">new</span> <span class="hljs-type">LoggingHandler</span>(LogLevel.DEBUG));Copy<br></code></pre></td></tr></table></figure>

<p><strong>运行结果</strong></p>
<figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs asciidoc">8222 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xbc122d07, L:/127.0.0.1:8080 - R:/127.0.0.1:52954] READ: 16B<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| 61 61 61 61 00 00 00 00 00 00 00 00 00 00 00 00 |aaaa............|</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><br>8222 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xbc122d07, L:/127.0.0.1:8080 - R:/127.0.0.1:52954] READ: 16B<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| 62 62 62 00 00 00 00 00 00 00 00 00 00 00 00 00 |bbb.............|</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><br><br>8222 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xbc122d07, L:/127.0.0.1:8080 - R:/127.0.0.1:52954] READ: 16B<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| 63 63 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |cc..............|</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><br><span class="hljs-bullet">... </span><br></code></pre></td></tr></table></figure>

<h4 id="行解码器"><a href="#行解码器" class="headerlink" title="行解码器"></a>行解码器</h4><p>行解码器的是<strong>通过分隔符对数据进行拆分</strong>来解决粘包半包问题的</p>
<p>可以通过<code>LineBasedFrameDecoder(int maxLength)</code>来拆分以<strong>换行符(\n)<strong>为分隔符的数据，也可以通过<code>DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf... delimiters)</code>来</strong>指定通过什么分隔符来拆分数据（可以传入多个分隔符）</strong></p>
<p>两种解码器<strong>都需要传入数据的最大长度</strong>，若超出最大长度，会抛出<code>TooLongFrameException</code>异常</p>
<p><strong>以换行符 \n 为分隔符</strong></p>
<p>客户端代码</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-comment">// 约定最大长度为 64</span><br>final <span class="hljs-built_in">int</span> maxLength = <span class="hljs-number">64</span>;<br><span class="hljs-comment">// 被发送的数据</span><br><span class="hljs-built_in">char</span> c = <span class="hljs-character">&#x27;a&#x27;</span>;<br><span class="hljs-keyword">for</span> (<span class="hljs-built_in">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) &#123;<br>    ByteBuf buffer = ctx.alloc<span class="hljs-literal">()</span>.buffer(maxLength);<br>    <span class="hljs-comment">// 生成长度为0~62的数据</span><br>    Random random = <span class="hljs-keyword">new</span> <span class="hljs-constructor">Random()</span>;<br>    StringBuilder sb = <span class="hljs-keyword">new</span> <span class="hljs-constructor">StringBuilder()</span>;<br>    <span class="hljs-keyword">for</span> (<span class="hljs-built_in">int</span> j = <span class="hljs-number">0</span>; j &lt; (<span class="hljs-built_in">int</span>)(random.next<span class="hljs-constructor">Int(<span class="hljs-params">maxLength</span>-2)</span>); j++) &#123;<br>        sb.append(c);<br>    &#125;<br>    <span class="hljs-comment">// 数据以 \n 结尾</span><br>    sb.append(<span class="hljs-string">&quot;\n&quot;</span>);<br>    buffer.write<span class="hljs-constructor">Bytes(<span class="hljs-params">sb</span>.<span class="hljs-params">toString</span>()</span>.get<span class="hljs-constructor">Bytes(StandardCharsets.UTF_8)</span>);<br>    c++;<br>    <span class="hljs-comment">// 将数据发送给服务器</span><br>    ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">buffer</span>)</span>;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>服务器代码</p>
<figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs haxe"><span class="hljs-comment">// 通过行解码器对粘包数据进行拆分，以 \n 为分隔符</span><br><span class="hljs-comment">// 需要指定最大长度</span><br>ch.pipeline().addLast(<span class="hljs-keyword">new</span> <span class="hljs-type">DelimiterBasedFrameDecoder</span>(<span class="hljs-number">64</span>));<br>ch.pipeline().addLast(<span class="hljs-keyword">new</span> <span class="hljs-type">LoggingHandler</span>(LogLevel.DEBUG));Copy<br></code></pre></td></tr></table></figure>

<p>运行结果</p>
<figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs asciidoc">4184 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x9d6ac701, L:/127.0.0.1:8080 - R:/127.0.0.1:58282] READ: 10B<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| 61 61 61 61 61 61 61 61 61 61                   |aaaaaaaaaa      |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><br>4184 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x9d6ac701, L:/127.0.0.1:8080 - R:/127.0.0.1:58282] READ: 11B<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| 62 62 62 62 62 62 62 62 62 62 62                |bbbbbbbbbbb     |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><br>4184 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x9d6ac701, L:/127.0.0.1:8080 - R:/127.0.0.1:58282] READ: 2B<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| 63 63                                           |cc              |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><br><span class="hljs-bullet">... </span><br></code></pre></td></tr></table></figure>

<p><strong>以自定义分隔符 \c 为分隔符</strong></p>
<p>客户端代码</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">...<br>    <br><span class="hljs-comment">// 数据以 \c 结尾</span><br>sb.append(<span class="hljs-string">&quot;\\c&quot;</span>);<br>buffer.write<span class="hljs-constructor">Bytes(<span class="hljs-params">sb</span>.<span class="hljs-params">toString</span>()</span>.get<span class="hljs-constructor">Bytes(StandardCharsets.UTF_8)</span>);<span class="hljs-operator"></span><br><span class="hljs-operator"></span><br><span class="hljs-operator">... </span><br></code></pre></td></tr></table></figure>

<p>服务器代码</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-comment">// 将分隔符放入ByteBuf中</span><br>ByteBuf bufSet = ch.alloc<span class="hljs-literal">()</span>.buffer<span class="hljs-literal">()</span>.write<span class="hljs-constructor">Bytes(<span class="hljs-string">&quot;\\c&quot;</span>.<span class="hljs-params">getBytes</span>(StandardCharsets.UTF_8)</span>);<br><span class="hljs-comment">// 通过行解码器对粘包数据进行拆分，以 \c 为分隔符</span><br>ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> DelimiterBasedFrameDecoder(64, <span class="hljs-params">ch</span>.<span class="hljs-params">alloc</span>()</span>.buffer<span class="hljs-literal">()</span>.write<span class="hljs-constructor">Bytes(<span class="hljs-params">bufSet</span>)</span>));<br>ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> LoggingHandler(LogLevel.DEBUG)</span>);Copy<br></code></pre></td></tr></table></figure>

<p>运行结果</p>
<figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs asciidoc">8246 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x86215ccd, L:/127.0.0.1:8080 - R:/127.0.0.1:65159] READ: 14B<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| 61 61 61 61 61 61 61 61 61 61 61 61 61 61       |aaaaaaaaaaaaaa  |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><br><br>8247 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x86215ccd, L:/127.0.0.1:8080 - R:/127.0.0.1:65159] READ: 3B<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| 62 62 62                                        |bbb             |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><br><span class="hljs-bullet">... </span><br></code></pre></td></tr></table></figure>

<h4 id="长度字段解码器"><a href="#长度字段解码器" class="headerlink" title="长度字段解码器"></a>长度字段解码器</h4><p>在传送数据时可以在数据中<strong>添加一个用于表示有用数据长度的字段</strong>，在解码时读取出这个用于表明长度的字段，同时读取其他相关参数，即可知道最终需要的数据是什么样子的</p>
<p><code>LengthFieldBasedFrameDecoder</code>解码器可以提供更为丰富的拆分方法，其构造方法有五个参数</p>
<figure class="highlight angelscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs angelscript"><span class="hljs-keyword">public</span> LengthFieldBasedFrameDecoder(<br>    <span class="hljs-built_in">int</span> maxFrameLength,<br>    <span class="hljs-built_in">int</span> lengthFieldOffset, <span class="hljs-built_in">int</span> lengthFieldLength,<br>    <span class="hljs-built_in">int</span> lengthAdjustment, <span class="hljs-built_in">int</span> initialBytesToStrip) <br></code></pre></td></tr></table></figure>

<p><strong>参数解析</strong></p>
<ul>
<li>maxFrameLength 数据最大长度<ul>
<li>表示数据的最大长度（包括附加信息、长度标识等内容）</li>
</ul>
</li>
<li>lengthFieldOffset <strong>数据长度标识的起始偏移量</strong><ul>
<li>用于指明数据第几个字节开始是用于标识有用字节长度的，因为前面可能还有其他附加信息</li>
</ul>
</li>
<li>lengthFieldLength <strong>数据长度标识所占字节数</strong>（用于指明有用数据的长度）<ul>
<li>数据中用于表示有用数据长度的标识所占的字节数</li>
</ul>
</li>
<li>lengthAdjustment <strong>长度表示与有用数据的偏移量</strong><ul>
<li>用于指明数据长度标识和有用数据之间的距离，因为两者之间还可能有附加信息</li>
</ul>
</li>
<li>initialBytesToStrip <strong>数据读取起点</strong><ul>
<li>读取起点，<strong>不读取</strong> 0 ~ initialBytesToStrip 之间的数据</li>
</ul>
</li>
</ul>
<p><strong>参数图解</strong></p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115811.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<figure class="highlight gherkin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs gherkin">lengthFieldOffset   = 0<br>lengthFieldLength   = 2<br>lengthAdjustment    = 0<br>initialBytesToStrip = 0 (= do not strip header)<br>  <br>BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)<br>+--------+----------------+      +--------+----------------+<br>|<span class="hljs-string"> Length </span>|<span class="hljs-string"> Actual Content </span>|<span class="hljs-string">-----&gt;</span>|<span class="hljs-string"> Length </span>|<span class="hljs-string"> Actual Content </span>|<br>|<span class="hljs-string"> 0x000C </span>|<span class="hljs-string"> &quot;HELLO, WORLD&quot; </span>|<span class="hljs-string">      </span>|<span class="hljs-string"> 0x000C </span>|<span class="hljs-string"> &quot;HELLO, WORLD&quot; </span>|<br>+--------+----------------+      +--------+----------------+ <br></code></pre></td></tr></table></figure>

<p>从0开始即为长度标识，长度标识长度为2个字节</p>
<p><strong>0x000C</strong> 即为后面 <code>HELLO, WORLD</code>的长度</p>
<hr>
<figure class="highlight gherkin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs gherkin">lengthFieldOffset   = 0<br>lengthFieldLength   = 2<br>lengthAdjustment    = 0<br>initialBytesToStrip = 2 (= the length of the Length field)<br>  <br>BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)<br>+--------+----------------+      +----------------+<br>|<span class="hljs-string"> Length </span>|<span class="hljs-string"> Actual Content </span>|<span class="hljs-string">-----&gt;</span>|<span class="hljs-string"> Actual Content </span>|<br>|<span class="hljs-string"> 0x000C </span>|<span class="hljs-string"> &quot;HELLO, WORLD&quot; </span>|<span class="hljs-string">      </span>|<span class="hljs-string"> &quot;HELLO, WORLD&quot; </span>|<br>+--------+----------------+      +----------------+ <br></code></pre></td></tr></table></figure>

<p>从0开始即为长度标识，长度标识长度为2个字节，<strong>读取时从第二个字节开始读取</strong>（此处即跳过长度标识）</p>
<p>因为<strong>跳过了用于表示长度的2个字节</strong>，所以此处直接读取<code>HELLO, WORLD</code></p>
<hr>
<figure class="highlight gherkin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs gherkin">lengthFieldOffset   = 2 (= the length of Header 1)<br>lengthFieldLength   = 3<br>lengthAdjustment    = 0<br>initialBytesToStrip = 0<br>  <br>BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)<br>+----------+----------+----------------+      +----------+----------+----------------+<br>|<span class="hljs-string"> Header 1 </span>|<span class="hljs-string">  Length  </span>|<span class="hljs-string"> Actual Content </span>|<span class="hljs-string">-----&gt;</span>|<span class="hljs-string"> Header 1 </span>|<span class="hljs-string">  Length  </span>|<span class="hljs-string"> Actual Content </span>|<br>|<span class="hljs-string">  0xCAFE  </span>|<span class="hljs-string"> 0x00000C </span>|<span class="hljs-string"> &quot;HELLO, WORLD&quot; </span>|<span class="hljs-string">      </span>|<span class="hljs-string">  0xCAFE  </span>|<span class="hljs-string"> 0x00000C </span>|<span class="hljs-string"> &quot;HELLO, WORLD&quot; </span>|<br>+----------+----------+----------------+      +----------+----------+----------------+ <br></code></pre></td></tr></table></figure>

<p>长度标识<strong>前面还有2个字节的其他内容</strong>（0xCAFE），第三个字节开始才是长度标识，长度表示长度为3个字节(0x00000C)</p>
<p>Header1中有附加信息，<strong>读取长度标识时需要跳过这些附加信息来获取长度</strong></p>
<hr>
<figure class="highlight gherkin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs gherkin">lengthFieldOffset   = 0<br>lengthFieldLength   = 3<br>lengthAdjustment    = 2 (= the length of Header 1)<br>initialBytesToStrip = 0<br>  <br>BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)<br>+----------+----------+----------------+      +----------+----------+----------------+<br>|<span class="hljs-string">  Length  </span>|<span class="hljs-string"> Header 1 </span>|<span class="hljs-string"> Actual Content </span>|<span class="hljs-string">-----&gt;</span>|<span class="hljs-string">  Length  </span>|<span class="hljs-string"> Header 1 </span>|<span class="hljs-string"> Actual Content </span>|<br>|<span class="hljs-string"> 0x00000C </span>|<span class="hljs-string">  0xCAFE  </span>|<span class="hljs-string"> &quot;HELLO, WORLD&quot; </span>|<span class="hljs-string">      </span>|<span class="hljs-string"> 0x00000C </span>|<span class="hljs-string">  0xCAFE  </span>|<span class="hljs-string"> &quot;HELLO, WORLD&quot; </span>|<br>+----------+----------+----------------+      +----------+----------+----------------+ <br></code></pre></td></tr></table></figure>

<p>从0开始即为长度标识，长度标识长度为3个字节，<strong>长度标识之后还有2个字节的其他内容</strong>（0xCAFE）</p>
<p>长度标识(0x00000C)表示的是**从其后lengthAdjustment（2个字节）开始的数据的长度，即<code>HELLO, WORLD</code>**，不包括0xCAFE</p>
<hr>
<figure class="highlight gherkin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs gherkin">lengthFieldOffset   = 1 (= the length of HDR1)<br>lengthFieldLength   = 2<br>lengthAdjustment    = 1 (= the length of HDR2)<br>initialBytesToStrip = 3 (= the length of HDR1 + LEN)<br>  <br>BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)<br>+------+--------+------+----------------+      +------+----------------+<br>|<span class="hljs-string"> HDR1 </span>|<span class="hljs-string"> Length </span>|<span class="hljs-string"> HDR2 </span>|<span class="hljs-string"> Actual Content </span>|<span class="hljs-string">-----&gt;</span>|<span class="hljs-string"> HDR2 </span>|<span class="hljs-string"> Actual Content </span>|<br>|<span class="hljs-string"> 0xCA </span>|<span class="hljs-string"> 0x000C </span>|<span class="hljs-string"> 0xFE </span>|<span class="hljs-string"> &quot;HELLO, WORLD&quot; </span>|<span class="hljs-string">      </span>|<span class="hljs-string"> 0xFE </span>|<span class="hljs-string"> &quot;HELLO, WORLD&quot; </span>|<br>+------+--------+------+----------------+      +------+----------------+ <br></code></pre></td></tr></table></figure>

<p>长度标识<strong>前面有1个字节的其他内容，后面也有1个字节的其他内容，读取时从长度标识之后3个字节处开始读取</strong>，即读取 <code>0xFE HELLO, WORLD</code></p>
<hr>
<p><strong>使用</strong></p>
<p>通过 <strong>EmbeddedChannel</strong> 对 handler 进行测试</p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs arduino"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EncoderStudy</span> &#123;</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">String</span>[] args)</span> </span>&#123;<br>        <span class="hljs-comment">// 模拟服务器</span><br>        <span class="hljs-comment">// 使用EmbeddedChannel测试handler</span><br>        EmbeddedChannel channel = <span class="hljs-keyword">new</span> <span class="hljs-built_in">EmbeddedChannel</span>(<br>                <span class="hljs-comment">// 数据最大长度为1KB，长度标识前后各有1个字节的附加信息，长度标识长度为4个字节（int）</span><br>                <span class="hljs-keyword">new</span> <span class="hljs-built_in">LengthFieldBasedFrameDecoder</span>(<span class="hljs-number">1024</span>, <span class="hljs-number">1</span>, <span class="hljs-number">4</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>),<br>                <span class="hljs-keyword">new</span> <span class="hljs-built_in">LoggingHandler</span>(LogLevel.DEBUG)<br>        );<br><br>        <span class="hljs-comment">// 模拟客户端，写入数据</span><br>        ByteBuf buffer = ByteBufAllocator.<span class="hljs-literal">DEFAULT</span>.<span class="hljs-built_in">buffer</span>();<br>        <span class="hljs-built_in">send</span>(buffer, <span class="hljs-string">&quot;Hello&quot;</span>);<br>        channel.<span class="hljs-built_in">writeInbound</span>(buffer);<br>        <span class="hljs-built_in">send</span>(buffer, <span class="hljs-string">&quot;World&quot;</span>);<br>        channel.<span class="hljs-built_in">writeInbound</span>(buffer);<br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">send</span><span class="hljs-params">(ByteBuf buf, <span class="hljs-keyword">String</span> msg)</span> </span>&#123;<br>        <span class="hljs-comment">// 得到数据的长度</span><br>        <span class="hljs-keyword">int</span> length = msg.<span class="hljs-built_in">length</span>();<br>        <span class="hljs-keyword">byte</span>[] bytes = msg.<span class="hljs-built_in">getBytes</span>(StandardCharsets.UTF_8);<br>        <span class="hljs-comment">// 将数据信息写入buf</span><br>        <span class="hljs-comment">// 写入长度标识前的其他信息</span><br>        buf.<span class="hljs-built_in">writeByte</span>(<span class="hljs-number">0xCA</span>);<br>        <span class="hljs-comment">// 写入数据长度标识</span><br>        buf.<span class="hljs-built_in">writeInt</span>(length);<br>        <span class="hljs-comment">// 写入长度标识后的其他信息</span><br>        buf.<span class="hljs-built_in">writeByte</span>(<span class="hljs-number">0xFE</span>);<br>        <span class="hljs-comment">// 写入具体的数据</span><br>        buf.<span class="hljs-built_in">writeBytes</span>(bytes);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>运行结果</p>
<figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs asciidoc">146  [main] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xembedded, L:embedded - R:embedded] READ: 11B<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><span class="hljs-section">|00000000| ca 00 00 00 05 fe 48 65 6c 6c 6f                |......Hello     |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br><br>146  [main] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0xembedded, L:embedded - R:embedded] READ: 11B<br><span class="hljs-code">         +-------------------------------------------------+</span><br><span class="hljs-section">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="hljs-section">+--------+-------------------------------------------------+----------------+</span><br>|00000000| ca 00 00 00 05 fe 57 6f 72 6c 64                |......World     |<br><span class="hljs-code">+--------+</span>-------------------------------------------------<span class="hljs-code">+----------------+</span> <br></code></pre></td></tr></table></figure>

<h2 id="2、协议设计与解析"><a href="#2、协议设计与解析" class="headerlink" title="2、协议设计与解析"></a>2、协议设计与解析</h2><h3 id="协议的作用"><a href="#协议的作用" class="headerlink" title="协议的作用"></a>协议的作用</h3><p>TCP/IP 中消息传输基于流的方式，没有边界</p>
<p><strong>协议的目的就是划定消息的边界，制定通信双方要共同遵守的通信规则</strong></p>
<h3 id="Redis协议"><a href="#Redis协议" class="headerlink" title="Redis协议"></a>Redis协议</h3><p>如果我们要向Redis服务器发送一条<code>set name Nyima</code>的指令，需要遵守如下协议</p>
<figure class="highlight taggerscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs taggerscript">// 该指令一共有3部分，每条指令之后都要添加回车与换行符<br>*3<span class="hljs-symbol">\r</span><span class="hljs-symbol">\n</span><br>// 第一个指令的长度是3<br>$3<span class="hljs-symbol">\r</span><span class="hljs-symbol">\n</span><br>// 第一个指令是set指令<br>set<span class="hljs-symbol">\r</span><span class="hljs-symbol">\n</span><br>// 下面的指令以此类推<br>$4<span class="hljs-symbol">\r</span><span class="hljs-symbol">\n</span><br>name<span class="hljs-symbol">\r</span><span class="hljs-symbol">\n</span><br>$5<span class="hljs-symbol">\r</span><span class="hljs-symbol">\n</span><br>Nyima<span class="hljs-symbol">\r</span><span class="hljs-symbol">\n</span>Copy<br></code></pre></td></tr></table></figure>

<p><strong>客户端代码如下</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> RedisClient &#123;<br>    static final Logger log = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">LoggerFactory</span>.</span></span>get<span class="hljs-constructor">Logger(StudyServer.<span class="hljs-params">class</span>)</span>;<br>    public static void main(String<span class="hljs-literal">[]</span> args) &#123;<br>        NioEventLoopGroup group =  <span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup()</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            ChannelFuture channelFuture = <span class="hljs-keyword">new</span> <span class="hljs-constructor">Bootstrap()</span><br>                    .group(group)<br>                    .channel(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">NioSocketChannel</span>.</span></span><span class="hljs-keyword">class</span>)<br>                    .handler(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;<span class="hljs-literal">()</span> &#123;<br>                        @Override<br>                        protected void init<span class="hljs-constructor">Channel(SocketChannel <span class="hljs-params">ch</span>)</span> &#123;<br>                            <span class="hljs-comment">// 打印日志</span><br>                            ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> LoggingHandler(LogLevel.DEBUG)</span>);<br>                            ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> ChannelInboundHandlerAdapter()</span> &#123;<br>                                @Override<br>                                public void channel<span class="hljs-constructor">Active(ChannelHandlerContext <span class="hljs-params">ctx</span>)</span> throws Exception &#123;<br>                                    <span class="hljs-comment">// 回车与换行符</span><br>                                    final byte<span class="hljs-literal">[]</span> LINE = &#123;<span class="hljs-character">&#x27;\r&#x27;</span>,<span class="hljs-character">&#x27;\n&#x27;</span>&#125;;<br>                                    <span class="hljs-comment">// 获得ByteBuf</span><br>                                    ByteBuf buffer = ctx.alloc<span class="hljs-literal">()</span>.buffer<span class="hljs-literal">()</span>;<br>                                    <span class="hljs-comment">// 连接建立后，向Redis中发送一条指令，注意添加回车与换行</span><br>                                    <span class="hljs-comment">// set name Nyima</span><br>                                    buffer.write<span class="hljs-constructor">Bytes(<span class="hljs-string">&quot;*3&quot;</span>.<span class="hljs-params">getBytes</span>()</span>);<br>                                    buffer.write<span class="hljs-constructor">Bytes(LINE)</span>;<br>                                    buffer.write<span class="hljs-constructor">Bytes(<span class="hljs-string">&quot;$3&quot;</span>.<span class="hljs-params">getBytes</span>()</span>);<br>                                    buffer.write<span class="hljs-constructor">Bytes(LINE)</span>;<br>                                    buffer.write<span class="hljs-constructor">Bytes(<span class="hljs-string">&quot;set&quot;</span>.<span class="hljs-params">getBytes</span>()</span>);<br>                                    buffer.write<span class="hljs-constructor">Bytes(LINE)</span>;<br>                                    buffer.write<span class="hljs-constructor">Bytes(<span class="hljs-string">&quot;$4&quot;</span>.<span class="hljs-params">getBytes</span>()</span>);<br>                                    buffer.write<span class="hljs-constructor">Bytes(LINE)</span>;<br>                                    buffer.write<span class="hljs-constructor">Bytes(<span class="hljs-string">&quot;name&quot;</span>.<span class="hljs-params">getBytes</span>()</span>);<br>                                    buffer.write<span class="hljs-constructor">Bytes(LINE)</span>;<br>                                    buffer.write<span class="hljs-constructor">Bytes(<span class="hljs-string">&quot;$5&quot;</span>.<span class="hljs-params">getBytes</span>()</span>);<br>                                    buffer.write<span class="hljs-constructor">Bytes(LINE)</span>;<br>                                    buffer.write<span class="hljs-constructor">Bytes(<span class="hljs-string">&quot;Nyima&quot;</span>.<span class="hljs-params">getBytes</span>()</span>);<br>                                    buffer.write<span class="hljs-constructor">Bytes(LINE)</span>;<br>                                    ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">buffer</span>)</span>;<br>                                &#125;<br><br>                            &#125;);<br>                        &#125;<br>                    &#125;)<br>                    .connect(<span class="hljs-keyword">new</span> <span class="hljs-constructor">InetSocketAddress(<span class="hljs-string">&quot;localhost&quot;</span>, 6379)</span>);<br>            channelFuture.sync<span class="hljs-literal">()</span>;<br>            <span class="hljs-comment">// 关闭channel</span><br>            channelFuture.channel<span class="hljs-literal">()</span>.close<span class="hljs-literal">()</span>.sync<span class="hljs-literal">()</span>;<br>        &#125; catch (InterruptedException e) &#123;<br>            e.print<span class="hljs-constructor">StackTrace()</span>;<br>        &#125; finally &#123;<br>            <span class="hljs-comment">// 关闭group</span><br>            group.shutdown<span class="hljs-constructor">Gracefully()</span>;<br>        &#125;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>控制台打印结果</strong></p>
<figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs tap">1600 [nioEventLoopGroup-2-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x28c994f1, L:/127.0.0.1:60792 - R:localhost/127.0.0.1:6379] WRITE: 34B<br>         +-------------------------------------------------+<br>         | <span class="hljs-number"> 0 </span><span class="hljs-number"> 1 </span><span class="hljs-number"> 2 </span><span class="hljs-number"> 3 </span><span class="hljs-number"> 4 </span><span class="hljs-number"> 5 </span><span class="hljs-number"> 6 </span><span class="hljs-number"> 7 </span><span class="hljs-number"> 8 </span><span class="hljs-number"> 9 </span> a  b  c  d  e  f |<br>+--------+-------------------------------------------------+----------------+<br>|00000000| 2a<span class="hljs-number"> 33 </span>0d 0a<span class="hljs-number"> 24 </span>33 0d 0a<span class="hljs-number"> 73 </span>65<span class="hljs-number"> 74 </span>0d 0a<span class="hljs-number"> 24 </span>34 0d |*3..$3..set..$4.|<br>|00000010| 0a 6e<span class="hljs-number"> 61 </span>6d<span class="hljs-number"> 65 </span>0d 0a<span class="hljs-number"> 24 </span>35 0d 0a 4e<span class="hljs-number"> 79 </span>69 6d<span class="hljs-number"> 61 </span>|.name..$5..Nyima|<br>|00000020| 0d 0a                                           |..              |<br>+--------+-------------------------------------------------+----------------+ <br></code></pre></td></tr></table></figure>

<p><strong>Redis中查询执行结果</strong></p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115832.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<h3 id="HTTP协议"><a href="#HTTP协议" class="headerlink" title="HTTP协议"></a>HTTP协议</h3><p>HTTP协议在请求行请求头中都有很多的内容，自己实现较为困难，可以使用<code>HttpServerCodec</code>作为<strong>服务器端的解码器与编码器，来处理HTTP请求</strong></p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs scala"><span class="hljs-comment">// HttpServerCodec 中既有请求的解码器 HttpRequestDecoder 又有响应的编码器 HttpResponseEncoder</span><br><span class="hljs-comment">// Codec(CodeCombine) 一般代表该类既作为 编码器 又作为 解码器</span><br>public <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HttpServerCodec</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">CombinedChannelDuplexHandler&lt;HttpRequestDecoder</span>, <span class="hljs-title">HttpResponseEncoder&gt;</span></span><br>        implements <span class="hljs-type">HttpServerUpgradeHandler</span>.<span class="hljs-type">SourceCodecCopy</span><br></code></pre></td></tr></table></figure>

<p><strong>服务器代码</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> HttpServer &#123;<br>    static final Logger log = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">LoggerFactory</span>.</span></span>get<span class="hljs-constructor">Logger(StudyServer.<span class="hljs-params">class</span>)</span>;<br><br>    public static void main(String<span class="hljs-literal">[]</span> args) &#123;<br>        NioEventLoopGroup group = <span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup()</span>;<br>        <span class="hljs-keyword">new</span> <span class="hljs-constructor">ServerBootstrap()</span><br>                .group(group)<br>                .channel(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">NioServerSocketChannel</span>.</span></span><span class="hljs-keyword">class</span>)<br>                .child<span class="hljs-constructor">Handler(<span class="hljs-params">new</span> ChannelInitializer&lt;SocketChannel&gt;()</span> &#123;<br>                    @Override<br>                    protected void init<span class="hljs-constructor">Channel(SocketChannel <span class="hljs-params">ch</span>)</span> &#123;<br>                        ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> LoggingHandler(LogLevel.DEBUG)</span>);<br>                        <span class="hljs-comment">// 作为服务器，使用 HttpServerCodec 作为编码器与解码器</span><br>                        ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> HttpServerCodec()</span>);<br>                        <span class="hljs-comment">// 服务器只处理HTTPRequest</span><br>                        ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> SimpleChannelInboundHandler&lt;HttpRequest&gt;()</span> &#123;<br>                            @Override<br>                            protected void channel<span class="hljs-constructor">Read0(ChannelHandlerContext <span class="hljs-params">ctx</span>, HttpRequest <span class="hljs-params">msg</span>)</span> &#123;<br>                                <span class="hljs-comment">// 获得请求uri</span><br>                                log.debug(msg.uri<span class="hljs-literal">()</span>);<br><br>                                <span class="hljs-comment">// 获得完整响应，设置版本号与状态码</span><br>                                DefaultFullHttpResponse response = <span class="hljs-keyword">new</span> <span class="hljs-constructor">DefaultFullHttpResponse(<span class="hljs-params">msg</span>.<span class="hljs-params">protocolVersion</span>()</span>, HttpResponseStatus.OK);<br>                                <span class="hljs-comment">// 设置响应内容</span><br>                                byte<span class="hljs-literal">[]</span> <span class="hljs-built_in">bytes</span> = <span class="hljs-string">&quot;&lt;h1&gt;Hello, World!&lt;/h1&gt;&quot;</span>.get<span class="hljs-constructor">Bytes(StandardCharsets.UTF_8)</span>;<br>                                <span class="hljs-comment">// 设置响应体长度，避免浏览器一直接收响应内容</span><br>                                response.headers<span class="hljs-literal">()</span>.set<span class="hljs-constructor">Int(CONTENT_LENGTH, <span class="hljs-params">bytes</span>.<span class="hljs-params">length</span>)</span>;<br>                                <span class="hljs-comment">// 设置响应体</span><br>                                response.content<span class="hljs-literal">()</span>.write<span class="hljs-constructor">Bytes(<span class="hljs-params">bytes</span>)</span>;<br><br>                                <span class="hljs-comment">// 写回响应</span><br>                                ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">response</span>)</span>;<br>                            &#125;<br>                        &#125;);<br>                    &#125;<br>                &#125;)<br>                .bind(<span class="hljs-number">8080</span>);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>服务器负责处理请求并响应浏览器。所以<strong>只需要处理HTTP请求</strong>即可</p>
<figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs haxe"><span class="hljs-comment">// 服务器只处理HTTPRequest</span><br>ch.pipeline().addLast(<span class="hljs-keyword">new</span> <span class="hljs-type">SimpleChannelInboundHandler</span>&lt;HttpRequest&gt;() <br></code></pre></td></tr></table></figure>

<p>获得请求后，需要返回响应给浏览器。需要创建响应对象<code>DefaultFullHttpResponse</code>，设置HTTP版本号及状态码，为避免浏览器获得响应后，因为获得<code>CONTENT_LENGTH</code>而一直空转，需要添加<code>CONTENT_LENGTH</code>字段，表明响应体中数据的具体长度</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-comment">// 获得完整响应，设置版本号与状态码</span><br>DefaultFullHttpResponse response = <span class="hljs-keyword">new</span> <span class="hljs-constructor">DefaultFullHttpResponse(<span class="hljs-params">msg</span>.<span class="hljs-params">protocolVersion</span>()</span>, HttpResponseStatus.OK);<br><span class="hljs-comment">// 设置响应内容</span><br>byte<span class="hljs-literal">[]</span> <span class="hljs-built_in">bytes</span> = <span class="hljs-string">&quot;&lt;h1&gt;Hello, World!&lt;/h1&gt;&quot;</span>.get<span class="hljs-constructor">Bytes(StandardCharsets.UTF_8)</span>;<br><span class="hljs-comment">// 设置响应体长度，避免浏览器一直接收响应内容</span><br>response.headers<span class="hljs-literal">()</span>.set<span class="hljs-constructor">Int(CONTENT_LENGTH, <span class="hljs-params">bytes</span>.<span class="hljs-params">length</span>)</span>;<br><span class="hljs-comment">// 设置响应体</span><br>response.content<span class="hljs-literal">()</span>.write<span class="hljs-constructor">Bytes(<span class="hljs-params">bytes</span>)</span>;Copy<br></code></pre></td></tr></table></figure>

<p><strong>运行结果</strong></p>
<p>浏览器</p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115849.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<p>控制台</p>
<figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs tap">// 请求内容<br>1714 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x72630ef7, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:55503] READ: 688B<br>         +-------------------------------------------------+<br>         | <span class="hljs-number"> 0 </span><span class="hljs-number"> 1 </span><span class="hljs-number"> 2 </span><span class="hljs-number"> 3 </span><span class="hljs-number"> 4 </span><span class="hljs-number"> 5 </span><span class="hljs-number"> 6 </span><span class="hljs-number"> 7 </span><span class="hljs-number"> 8 </span><span class="hljs-number"> 9 </span> a  b  c  d  e  f |<br>+--------+-------------------------------------------------+----------------+<br>|00000000|<span class="hljs-number"> 47 </span>45<span class="hljs-number"> 54 </span>20 2f<span class="hljs-number"> 66 </span>61<span class="hljs-number"> 76 </span>69<span class="hljs-number"> 63 </span>6f 6e 2e<span class="hljs-number"> 69 </span>63 6f |GET /favicon.ico|<br>|00000010|<span class="hljs-number"> 20 </span>48<span class="hljs-number"> 54 </span>54<span class="hljs-number"> 50 </span>2f<span class="hljs-number"> 31 </span>2e<span class="hljs-number"> 31 </span>0d 0a<span class="hljs-number"> 48 </span>6f<span class="hljs-number"> 73 </span>74 3a | HTTP/1.1..Host:|<br>|00000020|<span class="hljs-number"> 20 </span>6c 6f<span class="hljs-number"> 63 </span>61 6c<span class="hljs-number"> 68 </span>6f<span class="hljs-number"> 73 </span>74 3a<span class="hljs-number"> 38 </span>30<span class="hljs-number"> 38 </span>30 0d | localhost:8080.|<br>|00000030| 0a<span class="hljs-number"> 43 </span>6f 6e 6e<span class="hljs-number"> 65 </span>63<span class="hljs-number"> 74 </span>69 6f 6e 3a<span class="hljs-number"> 20 </span>6b<span class="hljs-number"> 65 </span>65 |.Connection: kee|<br>|00000040|<span class="hljs-number"> 70 </span>2d<span class="hljs-number"> 61 </span>6c<span class="hljs-number"> 69 </span>76<span class="hljs-number"> 65 </span>0d 0a<span class="hljs-number"> 50 </span>72<span class="hljs-number"> 61 </span>67 6d<span class="hljs-number"> 61 </span>3a |p-alive..Pragma:|<br>....<br><br>// 响应内容<br>1716 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x72630ef7, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:55503] WRITE: 61B<br>         +-------------------------------------------------+<br>         | <span class="hljs-number"> 0 </span><span class="hljs-number"> 1 </span><span class="hljs-number"> 2 </span><span class="hljs-number"> 3 </span><span class="hljs-number"> 4 </span><span class="hljs-number"> 5 </span><span class="hljs-number"> 6 </span><span class="hljs-number"> 7 </span><span class="hljs-number"> 8 </span><span class="hljs-number"> 9 </span> a  b  c  d  e  f |<br>+--------+-------------------------------------------------+----------------+<br>|00000000|<span class="hljs-number"> 48 </span>54<span class="hljs-number"> 54 </span>50 2f<span class="hljs-number"> 31 </span>2e<span class="hljs-number"> 31 </span>20<span class="hljs-number"> 32 </span>30<span class="hljs-number"> 30 </span>20 4f 4b 0d |HTTP/1.1<span class="hljs-number"> 200 </span>OK.|<br>|00000010| 0a<span class="hljs-number"> 43 </span>6f 6e<span class="hljs-number"> 74 </span>65 6e<span class="hljs-number"> 74 </span>2d 4c<span class="hljs-number"> 65 </span>6e<span class="hljs-number"> 67 </span>74<span class="hljs-number"> 68 </span>3a |.Content-Length:|<br>|00000020|<span class="hljs-number"> 20 </span>32<span class="hljs-number"> 32 </span>0d 0a 0d 0a 3c<span class="hljs-number"> 68 </span>31 3e<span class="hljs-number"> 48 </span>65 6c 6c 6f | 22....&lt;h1&gt;Hello|<br>|00000030| 2c<span class="hljs-number"> 20 </span>57 6f<span class="hljs-number"> 72 </span>6c<span class="hljs-number"> 64 </span>21 3c 2f<span class="hljs-number"> 68 </span>31 3e          |, World!&lt;/h1&gt;   |<br>+--------+-------------------------------------------------+----------------+ <br></code></pre></td></tr></table></figure>

<h3 id="自定义协议"><a href="#自定义协议" class="headerlink" title="自定义协议"></a>自定义协议</h3><h4 id="组成要素"><a href="#组成要素" class="headerlink" title="组成要素"></a>组成要素</h4><ul>
<li><p><strong>魔数</strong>：用来在第一时间判定接收的数据是否为无效数据包</p>
</li>
<li><p><strong>版本号</strong>：可以支持协议的升级</p>
</li>
<li><p>序列化算法</p>
<p>：消息正文到底采用哪种序列化反序列化方式</p>
<ul>
<li>如：json、protobuf、hessian、jdk</li>
</ul>
</li>
<li><p><strong>指令类型</strong>：是登录、注册、单聊、群聊… 跟业务相关</p>
</li>
<li><p><strong>请求序号</strong>：为了双工通信，提供异步能力</p>
</li>
<li><p><strong>正文长度</strong></p>
</li>
<li><p><strong>消息正文</strong></p>
</li>
</ul>
<h4 id="编码器与解码器"><a href="#编码器与解码器" class="headerlink" title="编码器与解码器"></a>编码器与解码器</h4><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> MessageCodec extends ByteToMessageCodec&lt;Message&gt; &#123;<br><br>    @Override<br>    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception &#123;<br>        <span class="hljs-comment">// 设置魔数 4个字节</span><br>        out.write<span class="hljs-constructor">Bytes(<span class="hljs-params">new</span> <span class="hljs-params">byte</span>[]&#123;&#x27;N&#x27;,&#x27;Y&#x27;,&#x27;I&#x27;,&#x27;M&#x27;&#125;)</span>;<br>        <span class="hljs-comment">// 设置版本号 1个字节</span><br>        out.write<span class="hljs-constructor">Byte(1)</span>;<br>        <span class="hljs-comment">// 设置序列化方式 1个字节</span><br>        out.write<span class="hljs-constructor">Byte(1)</span>;<br>        <span class="hljs-comment">// 设置指令类型 1个字节</span><br>        out.write<span class="hljs-constructor">Byte(<span class="hljs-params">msg</span>.<span class="hljs-params">getMessageType</span>()</span>);<br>        <span class="hljs-comment">// 设置请求序号 4个字节</span><br>        out.write<span class="hljs-constructor">Int(<span class="hljs-params">msg</span>.<span class="hljs-params">getSequenceId</span>()</span>);<br>        <span class="hljs-comment">// 为了补齐为16个字节，填充1个字节的数据</span><br>        out.write<span class="hljs-constructor">Byte(0xff)</span>;<br><br>        <span class="hljs-comment">// 获得序列化后的msg</span><br>        ByteArrayOutputStream bos = <span class="hljs-keyword">new</span> <span class="hljs-constructor">ByteArrayOutputStream()</span>;<br>        ObjectOutputStream oos = <span class="hljs-keyword">new</span> <span class="hljs-constructor">ObjectOutputStream(<span class="hljs-params">bos</span>)</span>;<br>        oos.write<span class="hljs-constructor">Object(<span class="hljs-params">msg</span>)</span>;<br>        byte<span class="hljs-literal">[]</span> <span class="hljs-built_in">bytes</span> = bos.<span class="hljs-keyword">to</span><span class="hljs-constructor">ByteArray()</span>;<br><br>        <span class="hljs-comment">// 获得并设置正文长度 长度用4个字节标识</span><br>        out.write<span class="hljs-constructor">Int(<span class="hljs-params">bytes</span>.<span class="hljs-params">length</span>)</span>;<br>        <span class="hljs-comment">// 设置消息正文</span><br>        out.write<span class="hljs-constructor">Bytes(<span class="hljs-params">bytes</span>)</span>;<br>    &#125;<br><br>    @Override<br>    protected void decode(ChannelHandlerContext ctx, ByteBuf <span class="hljs-keyword">in</span>, List&lt;Object&gt; out) throws Exception &#123;<br>        <span class="hljs-comment">// 获取魔数</span><br>        <span class="hljs-built_in">int</span> magic = <span class="hljs-keyword">in</span>.read<span class="hljs-constructor">Int()</span>;<br>        <span class="hljs-comment">// 获取版本号</span><br>        byte version = <span class="hljs-keyword">in</span>.read<span class="hljs-constructor">Byte()</span>;<br>        <span class="hljs-comment">// 获得序列化方式</span><br>        byte seqType = <span class="hljs-keyword">in</span>.read<span class="hljs-constructor">Byte()</span>;<br>        <span class="hljs-comment">// 获得指令类型</span><br>        byte messageType = <span class="hljs-keyword">in</span>.read<span class="hljs-constructor">Byte()</span>;<br>        <span class="hljs-comment">// 获得请求序号</span><br>        <span class="hljs-built_in">int</span> sequenceId = <span class="hljs-keyword">in</span>.read<span class="hljs-constructor">Int()</span>;<br>        <span class="hljs-comment">// 移除补齐字节</span><br>        <span class="hljs-keyword">in</span>.read<span class="hljs-constructor">Byte()</span>;<br>        <span class="hljs-comment">// 获得正文长度</span><br>        <span class="hljs-built_in">int</span> length = <span class="hljs-keyword">in</span>.read<span class="hljs-constructor">Int()</span>;<br>        <span class="hljs-comment">// 获得正文</span><br>        byte<span class="hljs-literal">[]</span> <span class="hljs-built_in">bytes</span> = <span class="hljs-keyword">new</span> byte<span class="hljs-literal">[<span class="hljs-identifier">length</span>]</span>;<br>        <span class="hljs-keyword">in</span>.read<span class="hljs-constructor">Bytes(<span class="hljs-params">bytes</span>, 0, <span class="hljs-params">length</span>)</span>;<br>        ObjectInputStream ois = <span class="hljs-keyword">new</span> <span class="hljs-constructor">ObjectInputStream(<span class="hljs-params">new</span> ByteArrayInputStream(<span class="hljs-params">bytes</span>)</span>);<br>        Message message = (Message) ois.read<span class="hljs-constructor">Object()</span>;<br>		<span class="hljs-comment">// 将信息放入List中，传递给下一个handler</span><br>        out.add(message);<br>        <br>        <span class="hljs-comment">// 打印获得的信息正文</span><br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;===========魔数===========&quot;</span>);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(magic);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;===========版本号===========&quot;</span>);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(version);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;===========序列化方法===========&quot;</span>);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(seqType);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;===========指令类型===========&quot;</span>);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(messageType);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;===========请求序号===========&quot;</span>);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(sequenceId);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;===========正文长度===========&quot;</span>);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(length);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;===========正文===========&quot;</span>);<br>        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(message);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<ul>
<li><p>编码器与解码器方法源于<strong>父类ByteToMessageCodec</strong>，通过该类可以自定义编码器与解码器，<strong>泛型类型为被编码与被解码的类</strong>。此处使用了自定义类Message，代表消息</p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs scala">public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MessageCodec</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ByteToMessageCodec&lt;Message&gt;Copy</span></span><br></code></pre></td></tr></table></figure></li>
<li><p>编码器<strong>负责将附加信息与正文信息写入到ByteBuf中</strong>，其中附加信息<strong>总字节数最好为2n，不足需要补齐</strong>。正文内容如果为对象，需要通过<strong>序列化</strong>将其放入到ByteBuf中</p>
</li>
<li><p>解码器<strong>负责将ByteBuf中的信息取出，并放入List中</strong>，该List用于将信息传递给下一个handler</p>
</li>
</ul>
<p><strong>编写测试类</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> TestCodec &#123;<br>    static final org.slf4j.Logger log = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">LoggerFactory</span>.</span></span>get<span class="hljs-constructor">Logger(StudyServer.<span class="hljs-params">class</span>)</span>;<br>    public static void main(String<span class="hljs-literal">[]</span> args) throws Exception &#123;<br>        EmbeddedChannel channel = <span class="hljs-keyword">new</span> <span class="hljs-constructor">EmbeddedChannel()</span>;<br>        <span class="hljs-comment">// 添加解码器，避免粘包半包问题</span><br>        channel.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> LengthFieldBasedFrameDecoder(1024, 12, 4, 0, 0)</span>);<br>        channel.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> LoggingHandler(LogLevel.DEBUG)</span>);<br>        channel.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> MessageCodec()</span>);<br>        LoginRequestMessage user = <span class="hljs-keyword">new</span> <span class="hljs-constructor">LoginRequestMessage(<span class="hljs-string">&quot;Nyima&quot;</span>, <span class="hljs-string">&quot;123&quot;</span>)</span>;<br><br>        <span class="hljs-comment">// 测试编码与解码</span><br>        ByteBuf byteBuf = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">ByteBufAllocator</span>.</span><span class="hljs-module"><span class="hljs-identifier">DEFAULT</span>.</span></span>buffer<span class="hljs-literal">()</span>;<br>        <span class="hljs-keyword">new</span> <span class="hljs-constructor">MessageCodec()</span>.encode(null, user, byteBuf);<br>        channel.write<span class="hljs-constructor">Inbound(<span class="hljs-params">byteBuf</span>)</span>;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<ul>
<li>测试类中用到了LengthFieldBasedFrameDecoder，避免粘包半包问题</li>
<li>通过MessageCodec的encode方法将附加信息与正文写入到ByteBuf中，通过channel执行入站操作。入站时会调用decode方法进行解码</li>
</ul>
<p>运行结果</p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115904.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115917.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<h4 id="Sharable注解"><a href="#Sharable注解" class="headerlink" title="@Sharable注解"></a>@Sharable注解</h4><p>为了<strong>提高handler的复用率，可以将handler创建为handler对象</strong>，然后在不同的channel中使用该handler对象进行处理操作</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">LoggingHandler loggingHandler = <span class="hljs-keyword">new</span> <span class="hljs-constructor">LoggingHandler(LogLevel.DEBUG)</span>;<br><span class="hljs-comment">// 不同的channel中使用同一个handler对象，提高复用率</span><br>channel1.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">loggingHandler</span>)</span>;<br>channel2.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">loggingHandler</span>)</span>;Copy<br></code></pre></td></tr></table></figure>

<p>但是<strong>并不是所有的handler都能通过这种方法来提高复用率的</strong>，例如<code>LengthFieldBasedFrameDecoder</code>。如果多个channel中使用同一个LengthFieldBasedFrameDecoder对象，则可能发生如下问题</p>
<ul>
<li>channel1中收到了一个半包，LengthFieldBasedFrameDecoder发现不是一条完整的数据，则没有继续向下传播</li>
<li>此时channel2中也收到了一个半包，<strong>因为两个channel使用了同一个LengthFieldBasedFrameDecoder，存入其中的数据刚好拼凑成了一个完整的数据包</strong>。LengthFieldBasedFrameDecoder让该数据包继续向下传播，<strong>最终引发错误</strong></li>
</ul>
<p>为了提高handler的复用率，同时又避免出现一些并发问题，<strong>Netty中原生的handler中用<code>@Sharable</code>注解来标明，该handler能否在多个channel中共享。</strong></p>
<p><strong>只有带有该注解，才能通过对象的方式被共享</strong>，否则无法被共享</p>
<h4 id="自定义编解码器能否使用-Sharable注解"><a href="#自定义编解码器能否使用-Sharable注解" class="headerlink" title="自定义编解码器能否使用@Sharable注解"></a><strong>自定义编解码器能否使用@Sharable注解</strong></h4><p><strong>这需要根据自定义的handler的处理逻辑进行分析</strong></p>
<p>我们的MessageCodec本身接收的是LengthFieldBasedFrameDecoder处理之后的数据，那么数据肯定是完整的，按分析来说是可以添加@Sharable注解的</p>
<p>但是实际情况我们并<strong>不能</strong>添加该注解，会抛出异常信息<code>ChannelHandler cn.nyimac.study.day8.protocol.MessageCodec is not allowed to be shared</code></p>
<ul>
<li><p>因为MessageCodec<strong>继承自ByteToMessageCodec</strong>，ByteToMessageCodec类的注解如下</p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115931.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<p>这就意味着<strong>ByteToMessageCodec不能被多个channel所共享的</strong></p>
<ul>
<li>原因：<strong>因为该类的目标是：将ByteBuf转化为Message，意味着传进该handler的数据还未被处理过</strong>。所以传过来的ByteBuf<strong>可能并不是完整的数据</strong>，如果共享则会出现问题</li>
</ul>
</li>
</ul>
<p><strong>如果想要共享，需要怎么办呢？</strong></p>
<p>继承<strong>MessageToMessageDecoder</strong>即可。<strong>该类的目标是：将已经被处理的完整数据再次被处理。</strong>传过来的Message<strong>如果是被处理过的完整数据</strong>，那么被共享也就不会出现问题了，也就可以使用@Sharable注解了。实现方式与ByteToMessageCodec类似</p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs scala"><span class="hljs-meta">@ChannelHandler</span>.<span class="hljs-type">Sharable</span><br>public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MessageSharableCodec</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">MessageToMessageCodec&lt;ByteBuf</span>, <span class="hljs-title">Message&gt;</span> </span>&#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> void encode(<span class="hljs-type">ChannelHandlerContext</span> ctx, <span class="hljs-type">Message</span> msg, <span class="hljs-type">List</span>&lt;<span class="hljs-type">Object</span>&gt; out) <span class="hljs-keyword">throws</span> <span class="hljs-type">Exception</span> &#123;<br>        ...<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> void decode(<span class="hljs-type">ChannelHandlerContext</span> ctx, <span class="hljs-type">ByteBuf</span> msg, <span class="hljs-type">List</span>&lt;<span class="hljs-type">Object</span>&gt; out) <span class="hljs-keyword">throws</span> <span class="hljs-type">Exception</span> &#123;<br>		...<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h2 id="3、在线聊天室"><a href="#3、在线聊天室" class="headerlink" title="3、在线聊天室"></a>3、在线聊天室</h2><h3 id="聊天室业务"><a href="#聊天室业务" class="headerlink" title="聊天室业务"></a>聊天室业务</h3><h4 id="用户登录接口"><a href="#用户登录接口" class="headerlink" title="用户登录接口"></a>用户登录接口</h4><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">UserService</span> </span>&#123;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 登录</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> username 用户名</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> password 密码</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 登录成功返回 true, 否则返回 false</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">boolean</span> login(<span class="hljs-keyword">String</span> username, <span class="hljs-keyword">String</span> password);<br>&#125; <br></code></pre></td></tr></table></figure>

<h4 id="用户会话接口"><a href="#用户会话接口" class="headerlink" title="用户会话接口"></a>用户会话接口</h4><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">Session</span> </span>&#123;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 绑定会话</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> channel 哪个 channel 要绑定会话</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> username 会话绑定用户</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">void</span> bind(Channel channel, <span class="hljs-keyword">String</span> username);<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 解绑会话</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> channel 哪个 channel 要解绑会话</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">void</span> unbind(Channel channel);<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 获取属性</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> channel 哪个 channel</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> name 属性名</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 属性值</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">Object</span> getAttribute(Channel channel, <span class="hljs-keyword">String</span> name);<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 设置属性</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> channel 哪个 channel</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> name 属性名</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> value 属性值</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">void</span> setAttribute(Channel channel, <span class="hljs-keyword">String</span> name, <span class="hljs-keyword">Object</span> value);<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 根据用户名获取 channel</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> username 用户名</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> channel</span><br><span class="hljs-comment">     */</span><br>    Channel getChannel(<span class="hljs-keyword">String</span> username);<br>&#125; <br></code></pre></td></tr></table></figure>

<h4 id="群聊会话接口"><a href="#群聊会话接口" class="headerlink" title="群聊会话接口"></a>群聊会话接口</h4><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">GroupSession</span> </span>&#123;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 创建一个聊天组, 如果不存在才能创建成功, 否则返回 null</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> name 组名</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> members 成员</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 成功时返回组对象, 失败返回 null</span><br><span class="hljs-comment">     */</span><br>    Group createGroup(<span class="hljs-keyword">String</span> name, Set&lt;<span class="hljs-keyword">String</span>&gt; members);<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 加入聊天组</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> name 组名</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> member 成员名</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 如果组不存在返回 null, 否则返回组对象</span><br><span class="hljs-comment">     */</span><br>    Group joinMember(<span class="hljs-keyword">String</span> name, <span class="hljs-keyword">String</span> member);<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 移除组成员</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> name 组名</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> member 成员名</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 如果组不存在返回 null, 否则返回组对象</span><br><span class="hljs-comment">     */</span><br>    Group removeMember(<span class="hljs-keyword">String</span> name, <span class="hljs-keyword">String</span> member);<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 移除聊天组</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> name 组名</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 如果组不存在返回 null, 否则返回组对象</span><br><span class="hljs-comment">     */</span><br>    Group removeGroup(<span class="hljs-keyword">String</span> name);<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 获取组成员</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> name 组名</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 成员集合, 如果群不存在或没有成员会返回 empty set</span><br><span class="hljs-comment">     */</span><br>    Set&lt;<span class="hljs-keyword">String</span>&gt; getMembers(<span class="hljs-keyword">String</span> name);<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 获取组成员的 channel 集合, 只有在线的 channel 才会返回</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> name 组名</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 成员 channel 集合</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">List</span>&lt;Channel&gt; getMembersChannel(<span class="hljs-keyword">String</span> name);<br>    <br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 判断群聊是否一被创建</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> name 群聊名称</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 是否存在</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">boolean</span> isCreated(<span class="hljs-keyword">String</span> name);<br>&#125; <br></code></pre></td></tr></table></figure>

<h4 id="整体结构"><a href="#整体结构" class="headerlink" title="整体结构"></a>整体结构</h4><p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130115946.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130120103.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<ul>
<li>client包：存放客户端相关类</li>
<li>message包：存放各种类型的消息</li>
<li>protocol包：存放自定义协议</li>
<li>server包：存放服务器相关类<ul>
<li>service包：存放用户相关类</li>
<li>session包：单聊及群聊相关会话类</li>
</ul>
</li>
</ul>
<h4 id="客户端代码结构"><a href="#客户端代码结构" class="headerlink" title="客户端代码结构"></a>客户端代码结构</h4><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> ChatClient &#123;<br>    static final Logger log = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">LoggerFactory</span>.</span></span>get<span class="hljs-constructor">Logger(ChatClient.<span class="hljs-params">class</span>)</span>;<br>    public static void main(String<span class="hljs-literal">[]</span> args) &#123;<br>        NioEventLoopGroup group = <span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup()</span>;<br>        LoggingHandler loggingHandler = <span class="hljs-keyword">new</span> <span class="hljs-constructor">LoggingHandler(LogLevel.DEBUG)</span>;<br>        MessageSharableCodec messageSharableCodec = <span class="hljs-keyword">new</span> <span class="hljs-constructor">MessageSharableCodec()</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            Bootstrap bootstrap = <span class="hljs-keyword">new</span> <span class="hljs-constructor">Bootstrap()</span>;<br>            bootstrap.group(group);<br>            bootstrap.channel(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">NioSocketChannel</span>.</span></span><span class="hljs-keyword">class</span>);<br>            bootstrap.handler(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;<span class="hljs-literal">()</span> &#123;<br>                @Override<br>                protected void init<span class="hljs-constructor">Channel(SocketChannel <span class="hljs-params">ch</span>)</span> throws Exception &#123;<br>                    ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> ProtocolFrameDecoder()</span>);<br>                    ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">loggingHandler</span>)</span>;<br>                    ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">messageSharableCodec</span>)</span>;<br>                &#125;<br>            &#125;);<br>            Channel channel = bootstrap.connect<span class="hljs-literal">()</span>.sync<span class="hljs-literal">()</span>.channel<span class="hljs-literal">()</span>;<br>            channel.close<span class="hljs-constructor">Future()</span>.sync<span class="hljs-literal">()</span>;<br>        &#125; catch (InterruptedException e) &#123;<br>            e.print<span class="hljs-constructor">StackTrace()</span>;<br>        &#125; finally &#123;<br>           group.shutdown<span class="hljs-constructor">Gracefully()</span>;<br>        &#125;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h4 id="服务器代码结构"><a href="#服务器代码结构" class="headerlink" title="服务器代码结构"></a>服务器代码结构</h4><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> ChatServer &#123;<br>    static final Logger log = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">LoggerFactory</span>.</span></span>get<span class="hljs-constructor">Logger(ChatServer.<span class="hljs-params">class</span>)</span>;<br>    public static void main(String<span class="hljs-literal">[]</span> args) &#123;<br>        NioEventLoopGroup boss = <span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup()</span>;<br>        NioEventLoopGroup worker = <span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup()</span>;<br>        LoggingHandler loggingHandler = <span class="hljs-keyword">new</span> <span class="hljs-constructor">LoggingHandler(LogLevel.DEBUG)</span>;<br>        MessageSharableCodec messageSharableCodec = <span class="hljs-keyword">new</span> <span class="hljs-constructor">MessageSharableCodec()</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            ServerBootstrap bootstrap = <span class="hljs-keyword">new</span> <span class="hljs-constructor">ServerBootstrap()</span>;<br>            bootstrap.group(boss, worker);<br>            bootstrap.channel(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">NioServerSocketChannel</span>.</span></span><span class="hljs-keyword">class</span>);<br>            bootstrap.child<span class="hljs-constructor">Handler(<span class="hljs-params">new</span> ChannelInitializer&lt;SocketChannel&gt;()</span> &#123;<br>                @Override<br>                protected void init<span class="hljs-constructor">Channel(SocketChannel <span class="hljs-params">ch</span>)</span> throws Exception &#123;<br>                    ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> ProtocolFrameDecoder()</span>);<br>                    ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">loggingHandler</span>)</span>;<br>                    ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">messageSharableCodec</span>)</span>;<br>                &#125;<br>            &#125;);<br>            Channel channel = bootstrap.bind(<span class="hljs-number">8080</span>).sync<span class="hljs-literal">()</span>.channel<span class="hljs-literal">()</span>;<br>            channel.close<span class="hljs-constructor">Future()</span>.sync<span class="hljs-literal">()</span>;<br>        &#125; catch (InterruptedException e) &#123;<br>            e.print<span class="hljs-constructor">StackTrace()</span>;<br>        &#125; finally &#123;<br>            boss.shutdown<span class="hljs-constructor">Gracefully()</span>;<br>            worker.shutdown<span class="hljs-constructor">Gracefully()</span>;<br>        &#125;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h3 id="登录"><a href="#登录" class="headerlink" title="登录"></a>登录</h3><h4 id="客户端代码-1"><a href="#客户端代码-1" class="headerlink" title="客户端代码"></a>客户端代码</h4><p>客户端添加如下handler，<strong>分别处理登录、聊天等操作</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> ChannelInboundHandlerAdapter()</span> &#123;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 创建连接时执行的处理器，用于执行登陆操作</span><br><span class="hljs-comment">     */</span><br>    @Override<br>    public void channel<span class="hljs-constructor">Active(ChannelHandlerContext <span class="hljs-params">ctx</span>)</span> throws Exception &#123;<br>        <span class="hljs-comment">// 开辟额外线程，用于用户登陆及后续操作</span><br>        <span class="hljs-keyword">new</span> <span class="hljs-constructor">Thread(()</span>-&gt;&#123;<br>            Scanner scanner = <span class="hljs-keyword">new</span> <span class="hljs-constructor">Scanner(System.<span class="hljs-params">in</span>)</span>;<br>            <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;请输入用户名&quot;</span>);<br>            String username = scanner.next<span class="hljs-literal">()</span>;<br>            <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;请输入密码&quot;</span>);<br>            String password = scanner.next<span class="hljs-literal">()</span>;<br>            <span class="hljs-comment">// 创建包含登录信息的请求体</span><br>            LoginRequestMessage message = <span class="hljs-keyword">new</span> <span class="hljs-constructor">LoginRequestMessage(<span class="hljs-params">username</span>, <span class="hljs-params">password</span>)</span>;<br>            <span class="hljs-comment">// 发送到channel中</span><br>            ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">message</span>)</span>;<br>            <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;等待后续操作...&quot;</span>);<br>            <span class="hljs-comment">// 阻塞，直到登陆成功后CountDownLatch被设置为0</span><br>            <span class="hljs-keyword">try</span> &#123;<br>                waitLogin.await<span class="hljs-literal">()</span>;<br>            &#125; catch (InterruptedException e) &#123;<br>                e.print<span class="hljs-constructor">StackTrace()</span>;<br>            &#125;<br>            <span class="hljs-comment">// 执行后续操作</span><br>            <span class="hljs-keyword">if</span> (!loginStatus.get<span class="hljs-literal">()</span>) &#123;<br>                <span class="hljs-comment">// 登陆失败，关闭channel并返回</span><br>                ctx.channel<span class="hljs-literal">()</span>.close<span class="hljs-literal">()</span>;<br>                return;<br>            &#125;<br>            <span class="hljs-comment">// 登录成功后，执行其他操作</span><br>            <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) &#123;<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;==================================&quot;</span>);<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;send [username] [content]&quot;</span>);<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;gsend [group name] [content]&quot;</span>);<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;gcreate [group name] [m1,m2,m3...]&quot;</span>);<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;gmembers [group name]&quot;</span>);<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;gjoin [group name]&quot;</span>);<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;gquit [group name]&quot;</span>);<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;quit&quot;</span>);<br>                <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;==================================&quot;</span>);<br>                String command = scanner.next<span class="hljs-constructor">Line()</span>;<br>                <span class="hljs-comment">// 获得指令及其参数，并发送对应类型消息</span><br>                String<span class="hljs-literal">[]</span> commands = command.split(<span class="hljs-string">&quot; &quot;</span>);<br>                switch (commands<span class="hljs-literal">[<span class="hljs-number">0</span>]</span>)&#123;<br>                    case <span class="hljs-string">&quot;send&quot;</span>:<br>                        ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">new</span> ChatRequestMessage(<span class="hljs-params">username</span>, <span class="hljs-params">commands</span>[1], <span class="hljs-params">commands</span>[2])</span>);<br>                        break;<br>                    case <span class="hljs-string">&quot;gsend&quot;</span>:<br>                        ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">new</span> GroupChatRequestMessage(<span class="hljs-params">username</span>,<span class="hljs-params">commands</span>[1], <span class="hljs-params">commands</span>[2])</span>);<br>                        break;<br>                    case <span class="hljs-string">&quot;gcreate&quot;</span>:<br>                        <span class="hljs-comment">// 分割，获得群员名</span><br>                        String<span class="hljs-literal">[]</span> members = commands<span class="hljs-literal">[<span class="hljs-number">2</span>]</span>.split(<span class="hljs-string">&quot;,&quot;</span>);<br>                        Set&lt;String&gt; set = <span class="hljs-keyword">new</span> HashSet&lt;&gt;(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Arrays</span>.</span></span><span class="hljs-keyword">as</span><span class="hljs-constructor">List(<span class="hljs-params">members</span>)</span>);<br>                        <span class="hljs-comment">// 把自己加入到群聊中</span><br>                        set.add(username);<br>                        ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">new</span> GroupCreateRequestMessage(<span class="hljs-params">commands</span>[1],<span class="hljs-params">set</span>)</span>);<br>                        break;<br>                    case <span class="hljs-string">&quot;gmembers&quot;</span>:<br>                        ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">new</span> GroupMembersRequestMessage(<span class="hljs-params">commands</span>[1])</span>);<br>                        break;<br>                    case <span class="hljs-string">&quot;gjoin&quot;</span>:<br>                        ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">new</span> GroupJoinRequestMessage(<span class="hljs-params">username</span>, <span class="hljs-params">commands</span>[1])</span>);<br>                        break;<br>                    case <span class="hljs-string">&quot;gquit&quot;</span>:<br>                        ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">new</span> GroupQuitRequestMessage(<span class="hljs-params">username</span>, <span class="hljs-params">commands</span>[1])</span>);<br>                        break;<br>                    case <span class="hljs-string">&quot;quit&quot;</span>:<br>                        ctx.channel<span class="hljs-literal">()</span>.close<span class="hljs-literal">()</span>;<br>                        return;<br>                    default:<br>                        <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-string">&quot;指令有误，请重新输入&quot;</span>);<br>                        continue;<br>                &#125;<br>            &#125;<br>        &#125;, <span class="hljs-string">&quot;login channel&quot;</span>).start<span class="hljs-literal">()</span>;<br>    &#125;<br><br>    @Override<br>    public void channel<span class="hljs-constructor">Read(ChannelHandlerContext <span class="hljs-params">ctx</span>, Object <span class="hljs-params">msg</span>)</span> throws Exception &#123;<br>        log.debug(<span class="hljs-string">&quot;&#123;&#125;&quot;</span>, msg);<br>        <span class="hljs-keyword">if</span> (msg instanceof LoginResponseMessage) &#123;<br>            <span class="hljs-comment">// 如果是登录响应信息</span><br>            LoginResponseMessage message = (LoginResponseMessage) msg;<br>            boolean isSuccess = message.is<span class="hljs-constructor">Success()</span>;<br>            <span class="hljs-comment">// 登录成功，设置登陆标记</span><br>            <span class="hljs-keyword">if</span> (isSuccess) &#123;<br>                loginStatus.set(<span class="hljs-literal">true</span>);<br>            &#125;<br>            <span class="hljs-comment">// 登陆后，唤醒登陆线程</span><br>            waitLogin.count<span class="hljs-constructor">Down()</span>;<br>        &#125;<br>    &#125;<br>&#125;);Copy<br></code></pre></td></tr></table></figure>

<h4 id="服务器代码-2"><a href="#服务器代码-2" class="headerlink" title="服务器代码"></a>服务器代码</h4><p>服务器添加如下handler，并添加到对应的channel中，<strong>负责处理登录请求信息，并作出响应</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">@ChannelHandler.Sharable <span class="hljs-comment">// 必须添加该注解</span><br>public <span class="hljs-keyword">class</span> LoginRequestMessageHandler extends SimpleChannelInboundHandler&lt;LoginRequestMessage&gt; &#123;<br>    @Override<br>    protected void channel<span class="hljs-constructor">Read0(ChannelHandlerContext <span class="hljs-params">ctx</span>, LoginRequestMessage <span class="hljs-params">msg</span>)</span> throws Exception &#123;<br>        <span class="hljs-comment">// 获得登录信息</span><br>        String username = msg.get<span class="hljs-constructor">Username()</span>;<br>        String password = msg.get<span class="hljs-constructor">Password()</span>;<br>        <span class="hljs-comment">// 校验登录信息</span><br>        boolean login = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">UserServiceFactory</span>.</span></span>get<span class="hljs-constructor">UserService()</span>.login(username, password);<br>        LoginResponseMessage message;<br>        <span class="hljs-keyword">if</span> (login) &#123;<br>            message = <span class="hljs-keyword">new</span> <span class="hljs-constructor">LoginResponseMessage(<span class="hljs-params">true</span>, <span class="hljs-string">&quot;登陆成功&quot;</span>)</span>;<br>            <span class="hljs-comment">// 绑定channel与user</span><br>            <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">SessionFactory</span>.</span></span>get<span class="hljs-constructor">Session()</span>.bind(ctx.channel<span class="hljs-literal">()</span>, username);<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            message = <span class="hljs-keyword">new</span> <span class="hljs-constructor">LoginResponseMessage(<span class="hljs-params">false</span>, <span class="hljs-string">&quot;登陆失败&quot;</span>)</span>;<br>        &#125;<br>        ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">message</span>)</span>;<br>    &#125;<br>&#125; <br><span class="hljs-comment">// 该handler处理登录请求</span><br>LoginRequestMessageHandler loginRequestMessageHandler = <span class="hljs-keyword">new</span> <span class="hljs-constructor">LoginRequestMessageHandler()</span>;<br>ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> LoginRequestMessageHandler()</span>);Copy<br></code></pre></td></tr></table></figure>

<h4 id="运行结果"><a href="#运行结果" class="headerlink" title="运行结果"></a>运行结果</h4><p><strong>客户端</strong></p>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs stylus"><span class="hljs-number">5665</span> <span class="hljs-selector-attr">[nioEventLoopGroup-2-1]</span> DEBUG cn<span class="hljs-selector-class">.nyimac</span><span class="hljs-selector-class">.study</span><span class="hljs-selector-class">.day8</span><span class="hljs-selector-class">.protocol</span><span class="hljs-selector-class">.MessageSharableCodec</span>  - <span class="hljs-number">1314474317</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">279</span><br><span class="hljs-number">5667</span> <span class="hljs-selector-attr">[nioEventLoopGroup-2-1]</span> DEBUG cn<span class="hljs-selector-class">.nyimac</span><span class="hljs-selector-class">.study</span><span class="hljs-selector-class">.day8</span><span class="hljs-selector-class">.protocol</span><span class="hljs-selector-class">.MessageSharableCodec</span>  - message:AbstractResponseMessage&#123;success=true, reason=<span class="hljs-string">&#x27;登陆成功&#x27;</span>&#125;<br><span class="hljs-number">5667</span> <span class="hljs-selector-attr">[nioEventLoopGroup-2-1]</span> DEBUG cn<span class="hljs-selector-class">.nyimac</span><span class="hljs-selector-class">.study</span><span class="hljs-selector-class">.day8</span><span class="hljs-selector-class">.client</span><span class="hljs-selector-class">.ChatClient</span>  - AbstractResponseMessage&#123;success=true, reason=<span class="hljs-string">&#x27;登陆成功&#x27;</span>&#125;<br>successCopy<br></code></pre></td></tr></table></figure>

<p><strong>服务器</strong></p>
<figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs tap">11919 [nioEventLoopGroup-3-1] DEBUG cn.nyimac.study.day8.protocol.MessageSharableCodec  - 1314474317, 1, 1, 0, 0, 217<br>11919 [nioEventLoopGroup-3-1] DEBUG cn.nyimac.study.day8.protocol.MessageSharableCodec  - message:LoginRequestMessage&#123;username=&#x27;Nyima&#x27;, password=&#x27;123&#x27;&#125;<br><br>7946 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x8e7c07f6, L:/127.0.0.1:8080 - R:/127.0.0.1:60572] WRITE: 295B<br>         +-------------------------------------------------+<br>         | <span class="hljs-number"> 0 </span><span class="hljs-number"> 1 </span><span class="hljs-number"> 2 </span><span class="hljs-number"> 3 </span><span class="hljs-number"> 4 </span><span class="hljs-number"> 5 </span><span class="hljs-number"> 6 </span><span class="hljs-number"> 7 </span><span class="hljs-number"> 8 </span><span class="hljs-number"> 9 </span> a  b  c  d  e  f |<br>+--------+-------------------------------------------------+----------------+<br>|00000000| 4e<span class="hljs-number"> 59 </span>49 4d<span class="hljs-number"> 01 </span>01<span class="hljs-number"> 01 </span>00<span class="hljs-number"> 00 </span>00<span class="hljs-number"> 00 </span>ff<span class="hljs-number"> 00 </span>00<span class="hljs-number"> 01 </span>17 |NYIM............|<br>|00000010| ac ed<span class="hljs-number"> 00 </span>05<span class="hljs-number"> 73 </span>72<span class="hljs-number"> 00 </span>31<span class="hljs-number"> 63 </span>6e 2e 6e<span class="hljs-number"> 79 </span>69 6d<span class="hljs-number"> 61 </span>|....sr.1cn.nyima|<br>|00000020|<span class="hljs-number"> 63 </span>2e<span class="hljs-number"> 73 </span>74<span class="hljs-number"> 75 </span>64<span class="hljs-number"> 79 </span>2e<span class="hljs-number"> 64 </span>61<span class="hljs-number"> 79 </span>38 2e 6d<span class="hljs-number"> 65 </span>73 |c.study.day8.mes|<br>|00000030|<span class="hljs-number"> 73 </span>61<span class="hljs-number"> 67 </span>65 2e 4c 6f<span class="hljs-number"> 67 </span>69 6e<span class="hljs-number"> 52 </span>65<span class="hljs-number"> 73 </span>70 6f 6e |sage.LoginRespon|<br>|00000040|<span class="hljs-number"> 73 </span>65 4d<span class="hljs-number"> 65 </span>73<span class="hljs-number"> 73 </span>61<span class="hljs-number"> 67 </span>65 e2<span class="hljs-number"> 34 </span>49<span class="hljs-number"> 24 </span>72<span class="hljs-number"> 52 </span>f3 |seMessage.4I$rR.|<br>|00000050|<span class="hljs-number"> 07 </span>02<span class="hljs-number"> 00 </span>00<span class="hljs-number"> 78 </span>72<span class="hljs-number"> 00 </span>34<span class="hljs-number"> 63 </span>6e 2e 6e<span class="hljs-number"> 79 </span>69 6d<span class="hljs-number"> 61 </span>|....xr.4cn.nyima|<br>|00000060|<span class="hljs-number"> 63 </span>2e<span class="hljs-number"> 73 </span>74<span class="hljs-number"> 75 </span>64<span class="hljs-number"> 79 </span>2e<span class="hljs-number"> 64 </span>61<span class="hljs-number"> 79 </span>38 2e 6d<span class="hljs-number"> 65 </span>73 |c.study.day8.mes|<br>|00000070|<span class="hljs-number"> 73 </span>61<span class="hljs-number"> 67 </span>65 2e<span class="hljs-number"> 41 </span>62<span class="hljs-number"> 73 </span>74<span class="hljs-number"> 72 </span>61<span class="hljs-number"> 63 </span>74<span class="hljs-number"> 52 </span>65<span class="hljs-number"> 73 </span>|sage.AbstractRes|<br>|00000080|<span class="hljs-number"> 70 </span>6f 6e<span class="hljs-number"> 73 </span>65 4d<span class="hljs-number"> 65 </span>73<span class="hljs-number"> 73 </span>61<span class="hljs-number"> 67 </span>65 b3 7e<span class="hljs-number"> 19 </span>32 |ponseMessage.~.2|<br>|00000090| 9b<span class="hljs-number"> 88 </span>4d 7b<span class="hljs-number"> 02 </span>00<span class="hljs-number"> 02 </span>5a<span class="hljs-number"> 00 </span>07<span class="hljs-number"> 73 </span>75<span class="hljs-number"> 63 </span>63<span class="hljs-number"> 65 </span>73 |..M&#123;...Z..succes|<br>|000000a0|<span class="hljs-number"> 73 </span>4c<span class="hljs-number"> 00 </span>06<span class="hljs-number"> 72 </span>65<span class="hljs-number"> 61 </span>73 6f 6e<span class="hljs-number"> 74 </span>00<span class="hljs-number"> 12 </span>4c 6a<span class="hljs-number"> 61 </span>|sL..reasont..Lja|<br>|000000b0|<span class="hljs-number"> 76 </span>61 2f 6c<span class="hljs-number"> 61 </span>6e<span class="hljs-number"> 67 </span>2f<span class="hljs-number"> 53 </span>74<span class="hljs-number"> 72 </span>69 6e<span class="hljs-number"> 67 </span>3b<span class="hljs-number"> 78 </span>|va/lang/String;x|<br>|000000c0|<span class="hljs-number"> 72 </span>00<span class="hljs-number"> 24 </span>63 6e 2e 6e<span class="hljs-number"> 79 </span>69 6d<span class="hljs-number"> 61 </span>63 2e<span class="hljs-number"> 73 </span>74<span class="hljs-number"> 75 </span>|r.$cn.nyimac.stu|<br>|000000d0|<span class="hljs-number"> 64 </span>79 2e<span class="hljs-number"> 64 </span>61<span class="hljs-number"> 79 </span>38 2e 6d<span class="hljs-number"> 65 </span>73<span class="hljs-number"> 73 </span>61<span class="hljs-number"> 67 </span>65 2e |dy.day8.message.|<br>|000000e0| 4d<span class="hljs-number"> 65 </span>73<span class="hljs-number"> 73 </span>61<span class="hljs-number"> 67 </span>65 dd e9<span class="hljs-number"> 84 </span>b7<span class="hljs-number"> 21 </span>db<span class="hljs-number"> 18 </span>52<span class="hljs-number"> 02 </span>|Message....!..R.|<br>|000000f0|<span class="hljs-number"> 00 </span>02<span class="hljs-number"> 49 </span>00 0b 6d<span class="hljs-number"> 65 </span>73<span class="hljs-number"> 73 </span>61<span class="hljs-number"> 67 </span>65<span class="hljs-number"> 54 </span>79<span class="hljs-number"> 70 </span>65 |..I..messageType|<br>|00000100|<span class="hljs-number"> 49 </span>00 0a<span class="hljs-number"> 73 </span>65<span class="hljs-number"> 71 </span>75<span class="hljs-number"> 65 </span>6e<span class="hljs-number"> 63 </span>65<span class="hljs-number"> 49 </span>64<span class="hljs-number"> 78 </span>70<span class="hljs-number"> 00 </span>|I..sequenceIdxp.|<br>|00000110|<span class="hljs-number"> 00 </span>00<span class="hljs-number"> 00 </span>00<span class="hljs-number"> 00 </span>00<span class="hljs-number"> 00 </span>01<span class="hljs-number"> 74 </span>00 0c e7<span class="hljs-number"> 99 </span>bb e9<span class="hljs-number"> 99 </span>|........t.......|<br>|00000120|<span class="hljs-number"> 86 </span>e6<span class="hljs-number"> 88 </span>90 e5 8a 9f                            |.......         |<br>+--------+-------------------------------------------------+----------------+ <br></code></pre></td></tr></table></figure>

<h3 id="单聊"><a href="#单聊" class="headerlink" title="单聊"></a>单聊</h3><p>客户端输入<code>send username content</code>即可发送单聊消息，需要<strong>服务器端添加处理ChatRequestMessage的handler</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">@ChannelHandler.Sharable <span class="hljs-comment">// 必须添加该注解</span><br>public <span class="hljs-keyword">class</span> ChatRequestMessageHandler extends SimpleChannelInboundHandler&lt;ChatRequestMessage&gt; &#123;<br>    @Override<br>    protected void channel<span class="hljs-constructor">Read0(ChannelHandlerContext <span class="hljs-params">ctx</span>, ChatRequestMessage <span class="hljs-params">msg</span>)</span> throws Exception &#123;<br>        <span class="hljs-comment">// 获得user所在的channel</span><br>        Channel channel = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">SessionFactory</span>.</span></span>get<span class="hljs-constructor">Session()</span>.get<span class="hljs-constructor">Channel(<span class="hljs-params">msg</span>.<span class="hljs-params">getTo</span>()</span>);<br>        <span class="hljs-comment">// 如果双方都在线</span><br>        <span class="hljs-keyword">if</span> (channel != null) &#123;<br>            <span class="hljs-comment">// 通过接收方与服务器之间的channel发送信息</span><br>            channel.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">new</span> ChatResponseMessage(<span class="hljs-params">msg</span>.<span class="hljs-params">getFrom</span>()</span>, msg.get<span class="hljs-constructor">Content()</span>));<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            <span class="hljs-comment">// 通过发送方与服务器之间的channel发送消息</span><br>            ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">new</span> ChatResponseMessage(<span class="hljs-params">false</span>, <span class="hljs-string">&quot;对方用户不存在或离线，发送失败&quot;</span>)</span>);<br>        &#125;<br>    &#125;<br>&#125; <br><span class="hljs-comment">// 该handler处理单聊请求</span><br>ChatRequestMessageHandler chatRequestMessageHandler = <span class="hljs-keyword">new</span> <span class="hljs-constructor">ChatRequestMessageHandler()</span>;<br>ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">chatRequestMessageHandler</span>)</span>;Copy<br></code></pre></td></tr></table></figure>

<p><strong>运行结果</strong></p>
<p>发送方（zhangsan）</p>
<figure class="highlight autoit"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs autoit"><span class="hljs-built_in">send</span> Nyima helloCopy<br></code></pre></td></tr></table></figure>

<p>接收方（Nyima）</p>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs stylus"><span class="hljs-comment">// 收到zhangsan发来的消息</span><br><span class="hljs-number">20230</span> <span class="hljs-selector-attr">[nioEventLoopGroup-2-1]</span> DEBUG cn<span class="hljs-selector-class">.nyimac</span><span class="hljs-selector-class">.study</span><span class="hljs-selector-class">.day8</span><span class="hljs-selector-class">.client</span><span class="hljs-selector-class">.ChatClient</span>  - ChatResponseMessage&#123;from=<span class="hljs-string">&#x27;zhangsan&#x27;</span>, <span class="hljs-attribute">content</span>=<span class="hljs-string">&#x27;hello&#x27;</span>&#125; <br></code></pre></td></tr></table></figure>

<h3 id="群聊"><a href="#群聊" class="headerlink" title="群聊"></a>群聊</h3><h4 id="创建-1"><a href="#创建-1" class="headerlink" title="创建"></a>创建</h4><p>添加处理<code>GroupCreateRequestMessage</code>的handler</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">@ChannelHandler.Sharable<br>public <span class="hljs-keyword">class</span> GroupCreateMessageHandler extends SimpleChannelInboundHandler&lt;GroupCreateRequestMessage&gt; &#123;<br>    @Override<br>    protected void channel<span class="hljs-constructor">Read0(ChannelHandlerContext <span class="hljs-params">ctx</span>, GroupCreateRequestMessage <span class="hljs-params">msg</span>)</span> throws Exception &#123;<br>        <span class="hljs-comment">// 获得要创建的群聊名</span><br>        String groupName = msg.get<span class="hljs-constructor">GroupName()</span>;<br>        <span class="hljs-comment">// 获得要创建的群聊的成员组</span><br>        Set&lt;String&gt; members = msg.get<span class="hljs-constructor">Members()</span>;<br>        <span class="hljs-comment">// 判断该群聊是否创建过，未创建返回null并创建群聊</span><br>        Group group = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">GroupSessionFactory</span>.</span></span>get<span class="hljs-constructor">GroupSession()</span>.create<span class="hljs-constructor">Group(<span class="hljs-params">groupName</span>, <span class="hljs-params">members</span>)</span>;<br>        <span class="hljs-keyword">if</span> (group<span class="hljs-operator"> == </span>null) &#123;<br>            <span class="hljs-comment">// 发送创建成功消息</span><br>            GroupCreateResponseMessage groupCreateResponseMessage = <span class="hljs-keyword">new</span> <span class="hljs-constructor">GroupCreateResponseMessage(<span class="hljs-params">true</span>, <span class="hljs-params">groupName</span> + <span class="hljs-string">&quot;创建成功&quot;</span>)</span>;<br>            ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">groupCreateResponseMessage</span>)</span>;<br>            <span class="hljs-comment">// 获得在线群员的channel，给群员发送入群聊消息</span><br>            List&lt;Channel&gt; membersChannel = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">GroupSessionFactory</span>.</span></span>get<span class="hljs-constructor">GroupSession()</span>.get<span class="hljs-constructor">MembersChannel(<span class="hljs-params">groupName</span>)</span>;<br>            groupCreateResponseMessage = <span class="hljs-keyword">new</span> <span class="hljs-constructor">GroupCreateResponseMessage(<span class="hljs-params">true</span>, <span class="hljs-string">&quot;您已被拉入&quot;</span>+<span class="hljs-params">groupName</span>)</span>;<br>            <span class="hljs-comment">// 给每个在线群员发送消息</span><br>            <span class="hljs-keyword">for</span>(Channel channel : membersChannel) &#123;<br>                channel.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">groupCreateResponseMessage</span>)</span>;<br>            &#125;<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            <span class="hljs-comment">// 发送失败消息</span><br>            GroupCreateResponseMessage groupCreateResponseMessage = <span class="hljs-keyword">new</span> <span class="hljs-constructor">GroupCreateResponseMessage(<span class="hljs-params">false</span>, <span class="hljs-params">groupName</span> + <span class="hljs-string">&quot;已存在&quot;</span>)</span>;<br>            ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">groupCreateResponseMessage</span>)</span>;<br>        &#125;<br>    &#125;<br>&#125; <br><span class="hljs-comment">// 该handler处理创建群聊请求</span><br>GroupCreateMessageHandler groupCreateMessageHandler = <span class="hljs-keyword">new</span> <span class="hljs-constructor">GroupCreateMessageHandler()</span>;<br>ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">groupCreateMessageHandler</span>)</span>;Copy<br></code></pre></td></tr></table></figure>

<p><strong>运行结果</strong></p>
<p>创建者客户端</p>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs stylus"><span class="hljs-comment">// 首次创建</span><br>gcreate Netty学习 zhangsan,lisi<br><br><span class="hljs-number">31649</span> <span class="hljs-selector-attr">[nioEventLoopGroup-2-1]</span> DEBUG cn<span class="hljs-selector-class">.nyimac</span><span class="hljs-selector-class">.study</span><span class="hljs-selector-class">.day8</span><span class="hljs-selector-class">.client</span><span class="hljs-selector-class">.ChatClient</span>  - AbstractResponseMessage&#123;success=true, reason=<span class="hljs-string">&#x27;Netty学习创建成功&#x27;</span>&#125;<br><span class="hljs-number">15244</span> <span class="hljs-selector-attr">[nioEventLoopGroup-2-1]</span> DEBUG cn<span class="hljs-selector-class">.nyimac</span><span class="hljs-selector-class">.study</span><span class="hljs-selector-class">.day8</span><span class="hljs-selector-class">.client</span><span class="hljs-selector-class">.ChatClient</span>  - AbstractResponseMessage&#123;success=true, reason=<span class="hljs-string">&#x27;您已被拉入Netty学习&#x27;</span>&#125;<br><br><br><span class="hljs-comment">// 再次创建</span><br>gcreate Netty学习 zhangsan,lisi<br><span class="hljs-number">40771</span> <span class="hljs-selector-attr">[nioEventLoopGroup-2-1]</span> DEBUG cn<span class="hljs-selector-class">.nyimac</span><span class="hljs-selector-class">.study</span><span class="hljs-selector-class">.day8</span><span class="hljs-selector-class">.client</span><span class="hljs-selector-class">.ChatClient</span>  - AbstractResponseMessage&#123;success=false, reason=<span class="hljs-string">&#x27;Netty学习已存在&#x27;</span>&#125; <br></code></pre></td></tr></table></figure>

<p>群员客户端</p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs basic"><span class="hljs-symbol">28788 </span>[nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;success=true, reason=<span class="hljs-comment">&#x27;您已被拉入Netty学习&#x27;&#125; </span><br></code></pre></td></tr></table></figure>

<h4 id="聊天"><a href="#聊天" class="headerlink" title="聊天"></a>聊天</h4><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">@ChannelHandler.Sharable<br>public <span class="hljs-keyword">class</span> GroupChatMessageHandler extends SimpleChannelInboundHandler&lt;GroupChatRequestMessage&gt; &#123;<br>    @Override<br>    protected void channel<span class="hljs-constructor">Read0(ChannelHandlerContext <span class="hljs-params">ctx</span>, GroupChatRequestMessage <span class="hljs-params">msg</span>)</span> throws Exception &#123;<br>        String groupName = msg.get<span class="hljs-constructor">GroupName()</span>;<br>        GroupSession groupSession = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">GroupSessionFactory</span>.</span></span>get<span class="hljs-constructor">GroupSession()</span>;<br>        <span class="hljs-comment">// 判断群聊是否存在</span><br>        boolean isCreated = groupSession.is<span class="hljs-constructor">Created(<span class="hljs-params">groupName</span>)</span>;<br>        <span class="hljs-keyword">if</span> (isCreated) &#123;<br>            <span class="hljs-comment">// 给群员发送信息</span><br>            List&lt;Channel&gt; membersChannel = groupSession.get<span class="hljs-constructor">MembersChannel(<span class="hljs-params">groupName</span>)</span>;<br>            <span class="hljs-keyword">for</span>(Channel channel : membersChannel) &#123;<br>                channel.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">new</span> GroupChatResponseMessage(<span class="hljs-params">msg</span>.<span class="hljs-params">getFrom</span>()</span>, msg.get<span class="hljs-constructor">Content()</span>));<br>            &#125;<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">new</span> GroupChatResponseMessage(<span class="hljs-params">false</span>, <span class="hljs-string">&quot;群聊不存在&quot;</span>)</span>);<br>        &#125;<br>    &#125;<br>&#125; <br><span class="hljs-comment">// 该handler处理群聊聊天</span><br>GroupChatMessageHandler groupChatMessageHandler = <span class="hljs-keyword">new</span> <span class="hljs-constructor">GroupChatMessageHandler()</span>;<br>ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">groupChatMessageHandler</span>)</span>;Copy<br></code></pre></td></tr></table></figure>

<p><strong>运行结果</strong></p>
<p>发送方（群聊存在）</p>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs stylus">gsend Netty学习 你们好<br><br><span class="hljs-number">45408</span> <span class="hljs-selector-attr">[nioEventLoopGroup-2-1]</span> DEBUG cn<span class="hljs-selector-class">.nyimac</span><span class="hljs-selector-class">.study</span><span class="hljs-selector-class">.day8</span><span class="hljs-selector-class">.client</span><span class="hljs-selector-class">.ChatClient</span>  - GroupChatResponseMessage&#123;from=<span class="hljs-string">&#x27;zhangsan&#x27;</span>, <span class="hljs-attribute">content</span>=<span class="hljs-string">&#x27;你们好&#x27;</span>&#125; <br></code></pre></td></tr></table></figure>

<p>接收方</p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs basic"><span class="hljs-symbol">48082 </span>[nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - GroupChatResponseMessage&#123;from=<span class="hljs-comment">&#x27;zhangsan&#x27;, content=&#x27;你们好&#x27;&#125; </span><br></code></pre></td></tr></table></figure>

<p>发送方（群聊不存在）</p>
<figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs routeros">gsend Spring学习 你们好<br><br>25140 [nioEventLoopGroup-2-1] <span class="hljs-builtin-name">DEBUG</span> cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;<span class="hljs-attribute">success</span>=<span class="hljs-literal">false</span>, <span class="hljs-attribute">reason</span>=<span class="hljs-string">&#x27;群聊不存在&#x27;</span>&#125; <br></code></pre></td></tr></table></figure>

<h4 id="加入"><a href="#加入" class="headerlink" title="加入"></a>加入</h4><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">@ChannelHandler.Sharable<br>public <span class="hljs-keyword">class</span> GroupJoinMessageHandler extends SimpleChannelInboundHandler&lt;GroupJoinRequestMessage&gt; &#123;<br>    @Override<br>    protected void channel<span class="hljs-constructor">Read0(ChannelHandlerContext <span class="hljs-params">ctx</span>, GroupJoinRequestMessage <span class="hljs-params">msg</span>)</span> throws Exception &#123;<br>        GroupSession groupSession = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">GroupSessionFactory</span>.</span></span>get<span class="hljs-constructor">GroupSession()</span>;<br>        <span class="hljs-comment">// 判断该用户是否在群聊中</span><br>        Set&lt;String&gt; members = groupSession.get<span class="hljs-constructor">Members(<span class="hljs-params">msg</span>.<span class="hljs-params">getGroupName</span>()</span>);<br>        boolean joinFlag = <span class="hljs-literal">false</span>;<br>        <span class="hljs-comment">// 群聊存在且用户未加入，才能加入</span><br>        <span class="hljs-keyword">if</span> (!members.contains(msg.get<span class="hljs-constructor">Username()</span>)<span class="hljs-operator"> &amp;&amp; </span>groupSession.is<span class="hljs-constructor">Created(<span class="hljs-params">msg</span>.<span class="hljs-params">getGroupName</span>()</span>)) &#123;<br>            joinFlag = <span class="hljs-literal">true</span>;<br>        &#125;<br><br>        <span class="hljs-keyword">if</span> (joinFlag) &#123;<br>            <span class="hljs-comment">// 加入群聊</span><br>            groupSession.join<span class="hljs-constructor">Member(<span class="hljs-params">msg</span>.<span class="hljs-params">getGroupName</span>()</span>, msg.get<span class="hljs-constructor">Username()</span>);<br>            ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">new</span> GroupJoinResponseMessage(<span class="hljs-params">true</span>,<span class="hljs-string">&quot;加入&quot;</span>+<span class="hljs-params">msg</span>.<span class="hljs-params">getGroupName</span>()</span>+<span class="hljs-string">&quot;成功&quot;</span>));<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">new</span> GroupJoinResponseMessage(<span class="hljs-params">false</span>, <span class="hljs-string">&quot;加入失败，群聊未存在或您已加入该群聊&quot;</span>)</span>);<br>        &#125;<br>    &#125;<br>&#125; <br><span class="hljs-comment">// 该handler处理加入群聊</span><br>GroupJoinMessageHandler groupJoinMessageHandler = <span class="hljs-keyword">new</span> <span class="hljs-constructor">GroupJoinMessageHandler()</span>;<br>ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">groupJoinMessageHandler</span>)</span>;Copy<br></code></pre></td></tr></table></figure>

<p><strong>运行结果</strong></p>
<p>正常加入群聊</p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs basic"><span class="hljs-symbol">94921 </span>[nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;success=true, reason=<span class="hljs-comment">&#x27;加入Netty学习成功&#x27;&#125; </span><br></code></pre></td></tr></table></figure>

<p>加入不能存在或已加入的群聊</p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs basic"><span class="hljs-symbol">44025 </span>[nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;success=false, reason=<span class="hljs-comment">&#x27;加入失败，群聊未存在或您已加入该群聊&#x27;&#125; </span><br></code></pre></td></tr></table></figure>

<h4 id="退出"><a href="#退出" class="headerlink" title="退出"></a>退出</h4><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">@ChannelHandler.Sharable<br>public <span class="hljs-keyword">class</span> GroupQuitMessageHandler extends SimpleChannelInboundHandler&lt;GroupQuitRequestMessage&gt; &#123;<br>    @Override<br>    protected void channel<span class="hljs-constructor">Read0(ChannelHandlerContext <span class="hljs-params">ctx</span>, GroupQuitRequestMessage <span class="hljs-params">msg</span>)</span> throws Exception &#123;<br>        GroupSession groupSession = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">GroupSessionFactory</span>.</span></span>get<span class="hljs-constructor">GroupSession()</span>;<br>        String groupName = msg.get<span class="hljs-constructor">GroupName()</span>;<br>        Set&lt;String&gt; members = groupSession.get<span class="hljs-constructor">Members(<span class="hljs-params">groupName</span>)</span>;<br>        String username = msg.get<span class="hljs-constructor">Username()</span>;<br>        <span class="hljs-comment">// 判断用户是否在群聊中以及群聊是否存在</span><br>        boolean joinFlag = <span class="hljs-literal">false</span>;<br>        <span class="hljs-keyword">if</span> (groupSession.is<span class="hljs-constructor">Created(<span class="hljs-params">groupName</span>)</span><span class="hljs-operator"> &amp;&amp; </span>members.contains(username)) &#123;<br>            <span class="hljs-comment">// 可以退出</span><br>            joinFlag = <span class="hljs-literal">true</span>;<br>        &#125;<br><br>        <span class="hljs-keyword">if</span> (joinFlag) &#123;<br>            <span class="hljs-comment">// 退出成功</span><br>            groupSession.remove<span class="hljs-constructor">Member(<span class="hljs-params">groupName</span>, <span class="hljs-params">username</span>)</span>;<br>            ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">new</span> GroupQuitResponseMessage(<span class="hljs-params">true</span>, <span class="hljs-string">&quot;退出&quot;</span>+<span class="hljs-params">groupName</span>+<span class="hljs-string">&quot;成功&quot;</span>)</span>);<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            <span class="hljs-comment">// 退出失败</span><br>            ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">new</span> GroupQuitResponseMessage(<span class="hljs-params">false</span>, <span class="hljs-string">&quot;群聊不存在或您未加入该群，退出&quot;</span>+<span class="hljs-params">groupName</span>+<span class="hljs-string">&quot;失败&quot;</span>)</span>);<br>        &#125;<br>    &#125;<br>&#125; <br><span class="hljs-comment">// 该handler处理退出群聊</span><br>GroupQuitMessageHandler groupQuitMessageHandler = <span class="hljs-keyword">new</span> <span class="hljs-constructor">GroupQuitMessageHandler()</span>;<br>ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">groupQuitMessageHandler</span>)</span>;Copy<br></code></pre></td></tr></table></figure>

<p><strong>运行结果</strong></p>
<p>正常退出</p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs basic"><span class="hljs-symbol">32282 </span>[nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;success=true, reason=<span class="hljs-comment">&#x27;退出Netty学习成功&#x27;&#125; </span><br></code></pre></td></tr></table></figure>

<p>退出不存在或未加入的群聊</p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs basic"><span class="hljs-symbol">67404 </span>[nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;success=false, reason=<span class="hljs-comment">&#x27;群聊不存在或您未加入该群，退出Netty失败&#x27;&#125; </span><br></code></pre></td></tr></table></figure>

<h4 id="查看成员"><a href="#查看成员" class="headerlink" title="查看成员"></a>查看成员</h4><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs scala"><span class="hljs-meta">@ChannelHandler</span>.<span class="hljs-type">Sharable</span><br>public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GroupMembersMessageHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SimpleChannelInboundHandler&lt;GroupMembersRequestMessage&gt;</span> </span>&#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> void channelRead0(<span class="hljs-type">ChannelHandlerContext</span> ctx, <span class="hljs-type">GroupMembersRequestMessage</span> msg) <span class="hljs-keyword">throws</span> <span class="hljs-type">Exception</span> &#123;<br>        ctx.writeAndFlush(<span class="hljs-keyword">new</span> <span class="hljs-type">GroupMembersResponseMessage</span>(<span class="hljs-type">GroupSessionFactory</span>.getGroupSession().getMembers(msg.getGroupName())));<br>    &#125;<br>&#125; <br><span class="hljs-comment">// 该handler处理查看成员</span><br><span class="hljs-type">GroupMembersMessageHandler</span> groupMembersMessageHandler = <span class="hljs-keyword">new</span> <span class="hljs-type">GroupMembersMessageHandler</span>();<br>ch.pipeline().addLast(groupMembersMessageHandler);<span class="hljs-type">Copy</span><br></code></pre></td></tr></table></figure>

<p><strong>运行结果</strong></p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs basic"><span class="hljs-symbol">46557 </span>[nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - GroupMembersResponseMessage&#123;members=[zhangsan, Nyima]&#125; <br></code></pre></td></tr></table></figure>

<h3 id="退出聊天室"><a href="#退出聊天室" class="headerlink" title="退出聊天室"></a>退出聊天室</h3><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs scala"><span class="hljs-meta">@ChannelHandler</span>.<span class="hljs-type">Sharable</span><br>public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">QuitHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ChannelInboundHandlerAdapter</span> </span>&#123;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 断开连接时触发 Inactive事件</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@Override</span><br>    public void channelInactive(<span class="hljs-type">ChannelHandlerContext</span> ctx) <span class="hljs-keyword">throws</span> <span class="hljs-type">Exception</span> &#123;<br>        <span class="hljs-comment">// 解绑</span><br>        <span class="hljs-type">SessionFactory</span>.getSession().unbind(ctx.channel());<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 异常退出，需要解绑</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@Override</span><br>    public void exceptionCaught(<span class="hljs-type">ChannelHandlerContext</span> ctx, <span class="hljs-type">Throwable</span> cause) <span class="hljs-keyword">throws</span> <span class="hljs-type">Exception</span> &#123;<br>        <span class="hljs-comment">// 解绑</span><br>        <span class="hljs-type">SessionFactory</span>.getSession().unbind(ctx.channel());<br>    &#125;<br>&#125; <br><span class="hljs-comment">// 该handler处理退出聊天室</span><br>ch.pipeline().addLast(quitHandler);<br><span class="hljs-type">GroupMembersMessageHandler</span> groupMembersMessageHandler = <span class="hljs-keyword">new</span> <span class="hljs-type">GroupMembersMessageHandler</span>();<span class="hljs-type">Copy</span><br></code></pre></td></tr></table></figure>

<p><strong>退出时，客户端会关闭channel并返回</strong></p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs arduino"><span class="hljs-keyword">case</span> <span class="hljs-string">&quot;quit&quot;</span>:<br>	<span class="hljs-comment">// 关闭channel并返回</span><br>    ctx.<span class="hljs-built_in">channel</span>().<span class="hljs-built_in">close</span>();<br>    <span class="hljs-keyword">return</span>;Copy<br></code></pre></td></tr></table></figure>

<h3 id="空闲检测"><a href="#空闲检测" class="headerlink" title="空闲检测"></a>空闲检测</h3><h4 id="连接假死"><a href="#连接假死" class="headerlink" title="连接假死"></a>连接假死</h4><p><strong>原因</strong></p>
<ul>
<li>网络设备出现故障，例如网卡，机房等，底层的 TCP 连接已经断开了，<strong>但应用程序没有感知到</strong>，仍然占用着资源</li>
<li>公网网络不稳定，出现丢包。如果连续出现丢包，这时现象就是客户端数据发不出去，服务端也一直收不到数据，会白白地消耗资源</li>
<li>应用程序线程阻塞，无法进行数据读写</li>
</ul>
<p><strong>问题</strong></p>
<ul>
<li>假死的连接占用的资源不能自动释放</li>
<li>向假死的连接发送数据，得到的反馈是发送超时</li>
</ul>
<h4 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h4><p>可以添加<code>IdleStateHandler</code>对空闲时间进行检测，通过构造函数可以传入三个参数</p>
<ul>
<li>readerIdleTimeSeconds 读空闲经过的秒数</li>
<li>writerIdleTimeSeconds 写空闲经过的秒数</li>
<li>allIdleTimeSeconds 读和写空闲经过的秒数</li>
</ul>
<p>当指定时间内未发生读或写事件时，<strong>会触发特定事件</strong></p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130120137.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<ul>
<li>读空闲会触发<code>READER_IDLE</code></li>
<li>写空闲会触发<code>WRITE_IDLE</code></li>
<li>读和写空闲会触发<code>ALL_IDEL</code></li>
</ul>
<p>想要处理这些事件，<strong>需要自定义事件处理函数</strong></p>
<p><strong>服务器端代码</strong></p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-comment">// 用于空闲连接的检测，5s内未读到数据，会触发READ_IDLE事件</span><br>ch.pipeline().addLast(<span class="hljs-keyword">new</span> IdleStateHandler(<span class="hljs-number">5</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>));<br><span class="hljs-comment">// 添加双向处理器，负责处理READER_IDLE事件</span><br>ch.pipeline().addLast(<span class="hljs-keyword">new</span> <span class="hljs-function"><span class="hljs-title">ChannelDuplexHandler</span>(<span class="hljs-params"></span>)</span> &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> userEventTriggered(ChannelHandlerContext ctx, <span class="hljs-built_in">Object</span> evt) throws Exception &#123;<br>        <span class="hljs-comment">// 获得事件</span><br>        IdleStateEvent event = (IdleStateEvent) evt;<br>        <span class="hljs-keyword">if</span> (event.state() == IdleState.READER_IDLE) &#123;<br>            <span class="hljs-comment">// 断开连接</span><br>            ctx.channel().close();<br>        &#125;<br>    &#125;<br>&#125;);Copy<br></code></pre></td></tr></table></figure>

<ul>
<li><p>使用<code>IdleStateHandler</code>进行空闲检测</p>
</li>
<li><p>使用双向处理器</p>
<figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ebnf"><span class="hljs-attribute">ChannelDuplexHandler</span><br></code></pre></td></tr></table></figure>

<p>对入站与出站事件进行处理</p>
<ul>
<li><code>IdleStateHandler</code>中的事件为特殊事件，需要实现<code>ChannelDuplexHandler</code>的<code>userEventTriggered</code>方法，判断事件类型并自定义处理方式，来对事件进行处理</li>
</ul>
</li>
</ul>
<p>为<strong>避免因非网络等原因引发的READ_IDLE事件</strong>，比如网络情况良好，只是用户本身没有输入数据，这时发生READ_IDLE事件，<strong>直接让服务器断开连接是不可取的</strong></p>
<p>为避免此类情况，需要在<strong>客户端向服务器发送心跳包</strong>，发送频率要<strong>小于</strong>服务器设置的<code>IdleTimeSeconds</code>，一般设置为其值的一半</p>
<p><strong>客户端代码</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-comment">// 发送心跳包，让服务器知道客户端在线</span><br><span class="hljs-comment">// 3s未发生WRITER_IDLE，就像服务器发送心跳包</span><br><span class="hljs-comment">// 该值为服务器端设置的READER_IDLE触发时间的一半左右</span><br>ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> IdleStateHandler(0, 3, 0)</span>);<br>ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> ChannelDuplexHandler()</span> &#123;<br>    @Override<br>    public void user<span class="hljs-constructor">EventTriggered(ChannelHandlerContext <span class="hljs-params">ctx</span>, Object <span class="hljs-params">evt</span>)</span> throws Exception &#123;<br>        IdleStateEvent event = (IdleStateEvent) evt;<br>        <span class="hljs-keyword">if</span> (event.state<span class="hljs-literal">()</span><span class="hljs-operator"> == </span>IdleState.WRITER_IDLE) &#123;<br>            <span class="hljs-comment">// 发送心跳包</span><br>            ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">new</span> PingMessage()</span>);<br>        &#125;<br>    &#125;<br>&#125;);Copy<br></code></pre></td></tr></table></figure>

<h1 id="五、优化"><a href="#五、优化" class="headerlink" title="五、优化"></a>五、优化</h1><h2 id="1、拓展序列化算法"><a href="#1、拓展序列化算法" class="headerlink" title="1、拓展序列化算法"></a>1、拓展序列化算法</h2><h3 id="序列化接口"><a href="#序列化接口" class="headerlink" title="序列化接口"></a>序列化接口</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">Serializer</span> </span>&#123;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 序列化</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> object 被序列化的对象</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> &lt;T&gt; 被序列化对象类型</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 序列化后的字节数组</span><br><span class="hljs-comment">     */</span><br>    &lt;T&gt; <span class="hljs-keyword">byte</span>[] serialize(T object);<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 反序列化</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> clazz 反序列化的目标类的Class对象</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> bytes 被反序列化的字节数组</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> &lt;T&gt; 反序列化目标类</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 反序列化后的对象</span><br><span class="hljs-comment">     */</span><br>    &lt;T&gt; <span class="hljs-function">T <span class="hljs-title">deserialize</span><span class="hljs-params">(Class&lt;T&gt; clazz, <span class="hljs-keyword">byte</span>[] bytes)</span></span>;<br>&#125; <br></code></pre></td></tr></table></figure>

<h3 id="枚举实现类"><a href="#枚举实现类" class="headerlink" title="枚举实现类"></a>枚举实现类</h3><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public enum SerializerAlgorithm implements Serializer &#123;<br>    <span class="hljs-comment">// Java的序列化和反序列化</span><br>    Java &#123;<br>        @Override<br>        public &lt;T&gt; byte<span class="hljs-literal">[]</span> serialize(T <span class="hljs-keyword">object</span>) &#123;<br>            <span class="hljs-comment">// 序列化后的字节数组</span><br>            byte<span class="hljs-literal">[]</span> <span class="hljs-built_in">bytes</span> = null;<br>            <span class="hljs-keyword">try</span> (ByteArrayOutputStream bos = <span class="hljs-keyword">new</span> <span class="hljs-constructor">ByteArrayOutputStream()</span>;<br>                 ObjectOutputStream oos = <span class="hljs-keyword">new</span> <span class="hljs-constructor">ObjectOutputStream(<span class="hljs-params">bos</span>)</span>) &#123;<br>                oos.write<span class="hljs-constructor">Object(<span class="hljs-params">object</span>)</span>;<br>                <span class="hljs-built_in">bytes</span> = bos.<span class="hljs-keyword">to</span><span class="hljs-constructor">ByteArray()</span>;<br>            &#125; catch (IOException e) &#123;<br>                e.print<span class="hljs-constructor">StackTrace()</span>;<br>            &#125;<br>            return <span class="hljs-built_in">bytes</span>;<br>        &#125;<br><br>        @Override<br>        public &lt;T&gt; T deserialize(Class&lt;T&gt; clazz, byte<span class="hljs-literal">[]</span> <span class="hljs-built_in">bytes</span>) &#123;<br>            T target = null;<br>            <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>out.println(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Arrays</span>.</span></span><span class="hljs-keyword">to</span><span class="hljs-constructor">String(<span class="hljs-params">bytes</span>)</span>);<br>            <span class="hljs-keyword">try</span> (ByteArrayInputStream bis = <span class="hljs-keyword">new</span> <span class="hljs-constructor">ByteArrayInputStream(<span class="hljs-params">bytes</span>)</span>;<br>                 ObjectInputStream ois = <span class="hljs-keyword">new</span> <span class="hljs-constructor">ObjectInputStream(<span class="hljs-params">bis</span>)</span>) &#123;<br>                target = (T) ois.read<span class="hljs-constructor">Object()</span>;<br>            &#125; catch (IOException <span class="hljs-pattern-match">| <span class="hljs-constructor">ClassNotFoundException</span> e) &#123;</span><br><span class="hljs-pattern-match">                e.print<span class="hljs-constructor">StackTrace()</span>;</span><br><span class="hljs-pattern-match">            &#125;</span><br><span class="hljs-pattern-match">            <span class="hljs-operator">/</span><span class="hljs-operator">/</span> 返回反序列化后的对象</span><br><span class="hljs-pattern-match">            return target;</span><br><span class="hljs-pattern-match">        &#125;</span><br><span class="hljs-pattern-match">    &#125;</span><br><span class="hljs-pattern-match">    </span><br><span class="hljs-pattern-match">     <span class="hljs-operator">/</span><span class="hljs-operator">/</span> <span class="hljs-constructor">Json</span>的序列化和反序列化</span><br><span class="hljs-pattern-match">    <span class="hljs-constructor">Json</span> &#123;</span><br><span class="hljs-pattern-match">        @<span class="hljs-constructor">Override</span></span><br><span class="hljs-pattern-match">        public &lt;<span class="hljs-constructor">T</span>&gt; byte[] serialize(<span class="hljs-constructor">T</span> <span class="hljs-keyword">object</span>) &#123;</span><br><span class="hljs-pattern-match">            <span class="hljs-constructor">String</span> s = <span class="hljs-keyword">new</span> <span class="hljs-constructor">Gson()</span>.<span class="hljs-keyword">to</span><span class="hljs-constructor">Json(<span class="hljs-params">object</span>)</span>;</span><br><span class="hljs-pattern-match">            <span class="hljs-constructor">System</span>.out.println(s);</span><br><span class="hljs-pattern-match">            <span class="hljs-operator">/</span><span class="hljs-operator">/</span> 指定字符集，获得字节数组</span><br><span class="hljs-pattern-match">            return s.get<span class="hljs-constructor">Bytes(StandardCharsets.UTF_8)</span>;</span><br><span class="hljs-pattern-match">        &#125;</span><br><span class="hljs-pattern-match"></span><br><span class="hljs-pattern-match">        @<span class="hljs-constructor">Override</span></span><br><span class="hljs-pattern-match">        public &lt;<span class="hljs-constructor">T</span>&gt; <span class="hljs-constructor">T</span> deserialize(<span class="hljs-constructor">Class</span>&lt;<span class="hljs-constructor">T</span>&gt; clazz, byte[] <span class="hljs-built_in">bytes</span>) &#123;</span><br><span class="hljs-pattern-match">            <span class="hljs-constructor">String</span> s = <span class="hljs-keyword">new</span> <span class="hljs-constructor">String(<span class="hljs-params">bytes</span>, StandardCharsets.UTF_8)</span>;</span><br><span class="hljs-pattern-match">            <span class="hljs-constructor">System</span>.out.println(s);</span><br><span class="hljs-pattern-match">            <span class="hljs-operator">/</span><span class="hljs-operator">/</span> 此处的clazz为具体类型的<span class="hljs-constructor">Class</span>对象，而不是父类<span class="hljs-constructor">Message</span>的</span><br><span class="hljs-pattern-match">            return <span class="hljs-keyword">new</span> <span class="hljs-constructor">Gson()</span>.from<span class="hljs-constructor">Json(<span class="hljs-params">s</span>, <span class="hljs-params">clazz</span>)</span>;</span><br><span class="hljs-pattern-match">        &#125;</span><br><span class="hljs-pattern-match">    &#125;</span><br><span class="hljs-pattern-match">&#125; </span><br></code></pre></td></tr></table></figure>

<h3 id="修改原编解码器"><a href="#修改原编解码器" class="headerlink" title="修改原编解码器"></a>修改原编解码器</h3><p><strong>编码</strong></p>
<figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs awk"><span class="hljs-regexp">//</span> 获得序列化后的msg<br><span class="hljs-regexp">//</span> 使用指定的序列化方式<br>SerializerAlgorithm[] values = SerializerAlgorithm.values();<br><span class="hljs-regexp">//</span> 获得序列化后的对象<br>byte[] bytes = values[out.getByte(<span class="hljs-number">5</span>)-<span class="hljs-number">1</span>].serialize(msg);Copy<br></code></pre></td></tr></table></figure>

<p><strong>解码</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-comment">// 获得反序列化方式</span><br>SerializerAlgorithm<span class="hljs-literal">[]</span> values = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">SerializerAlgorithm</span>.</span></span>values<span class="hljs-literal">()</span>;<br><span class="hljs-comment">// 通过指定方式进行反序列化</span><br><span class="hljs-comment">// 需要通过Message的方法获得具体的消息类型</span><br>Message message = values<span class="hljs-literal">[<span class="hljs-identifier">seqType</span>-<span class="hljs-number">1</span>]</span>.deserialize(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Message</span>.</span></span>get<span class="hljs-constructor">MessageClass(<span class="hljs-params">messageType</span>)</span>, <span class="hljs-built_in">bytes</span>);Copy<br></code></pre></td></tr></table></figure>

<h2 id="2、参数调优"><a href="#2、参数调优" class="headerlink" title="2、参数调优"></a>2、参数调优</h2><h3 id="CONNECT-TIMEOUT-MILLIS"><a href="#CONNECT-TIMEOUT-MILLIS" class="headerlink" title="CONNECT_TIMEOUT_MILLIS"></a>CONNECT_TIMEOUT_MILLIS</h3><ul>
<li>属于 <strong>SocketChannal</strong> 的参数</li>
<li>用在<strong>客户端建立连接</strong>时，如果在指定毫秒内无法连接，会抛出 timeout 异常</li>
<li><strong>注意</strong>：Netty 中不要用成了SO_TIMEOUT 主要用在阻塞 IO，而 Netty 是非阻塞 IO</li>
</ul>
<h4 id="使用"><a href="#使用" class="headerlink" title="使用"></a><strong>使用</strong></h4><figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs haxe"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestParam</span> </span>&#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> void main(<span class="hljs-keyword">String</span>[] args) &#123;<br>        <span class="hljs-comment">// SocketChannel 5s内未建立连接就抛出异常</span><br>        <span class="hljs-keyword">new</span> <span class="hljs-type">Bootstrap</span>().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, <span class="hljs-number">5000</span>);<br>        <br>        <span class="hljs-comment">// ServerSocketChannel 5s内未建立连接就抛出异常</span><br>        <span class="hljs-keyword">new</span> <span class="hljs-type">ServerBootstrap</span>().option(ChannelOption.CONNECT_TIMEOUT_MILLIS,<span class="hljs-number">5000</span>);<br>        <span class="hljs-comment">// SocketChannel 5s内未建立连接就抛出异常</span><br>        <span class="hljs-keyword">new</span> <span class="hljs-type">ServerBootstrap</span>().childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, <span class="hljs-number">5000</span>);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<ul>
<li><p>客户端通过 <code>Bootstrap.option</code> 函数来配置参数，<strong>配置参数作用于 SocketChannel</strong></p>
</li>
<li><p>服务器通过</p>
<figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ebnf"><span class="hljs-attribute">ServerBootstrap</span><br></code></pre></td></tr></table></figure>

<p>来配置参数，但是对于不同的 Channel 需要选择不同的方法</p>
<ul>
<li>通过 <code>option</code> 来配置 <strong>ServerSocketChannel</strong> 上的参数</li>
<li>通过 <code>childOption</code> 来配置 <strong>SocketChannel</strong> 上的参数</li>
</ul>
</li>
</ul>
<h4 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h4><p>客户端中连接服务器的线程是 NIO 线程，抛出异常的是主线程。这是如何做到超时判断以及线程通信的呢？</p>
<p><code>AbstractNioChannel.AbstractNioUnsafe.connect</code>方法中</p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs arduino"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-title">connect</span><span class="hljs-params">(</span></span><br><span class="hljs-params"><span class="hljs-function">                <span class="hljs-keyword">final</span> SocketAddress remoteAddress, <span class="hljs-keyword">final</span> SocketAddress localAddress, <span class="hljs-keyword">final</span> ChannelPromise promise)</span> </span>&#123;<br>    <br>    ...<br>        <br>    <span class="hljs-comment">// Schedule connect timeout.</span><br>    <span class="hljs-comment">// 设置超时时间，通过option方法传入的CONNECT_TIMEOUT_MILLIS参数进行设置</span><br>    <span class="hljs-keyword">int</span> connectTimeoutMillis = <span class="hljs-built_in">config</span>().<span class="hljs-built_in">getConnectTimeoutMillis</span>();<br>    <span class="hljs-comment">// 如果超时时间大于0</span><br>    <span class="hljs-keyword">if</span> (connectTimeoutMillis &gt; <span class="hljs-number">0</span>) &#123;<br>        <span class="hljs-comment">// 创建一个定时任务，延时connectTimeoutMillis（设置的超时时间时间）后执行</span><br>        <span class="hljs-comment">// schedule(Runnable command, long delay, TimeUnit unit)</span><br>        connectTimeoutFuture = <span class="hljs-built_in">eventLoop</span>().<span class="hljs-built_in">schedule</span>(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Runnable</span>() &#123;<br>            @Override<br>            <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-built_in">run</span>() &#123;<br>                <span class="hljs-comment">// 判断是否建立连接，Promise进行NIO线程与主线程之间的通信</span><br>                <span class="hljs-comment">// 如果超时，则通过tryFailure方法将异常放入Promise中</span><br>                <span class="hljs-comment">// 在主线程中抛出</span><br>                ChannelPromise connectPromise = AbstractNioChannel.<span class="hljs-keyword">this</span>.connectPromise;<br>                ConnectTimeoutException cause = <span class="hljs-keyword">new</span> <span class="hljs-built_in">ConnectTimeoutException</span>(<span class="hljs-string">&quot;connection timed out: &quot;</span> + remoteAddress);<br>                <span class="hljs-keyword">if</span> (connectPromise != null &amp;&amp; connectPromise.<span class="hljs-built_in">tryFailure</span>(cause)) &#123;<br>                    <span class="hljs-built_in">close</span>(<span class="hljs-built_in">voidPromise</span>());<br>                &#125;<br>            &#125;<br>        &#125;, connectTimeoutMillis, TimeUnit.MILLISECONDS);<br>    &#125;<br>    <br>   	...<br>        <br>&#125; <br></code></pre></td></tr></table></figure>

<p>超时的判断<strong>主要是通过 Eventloop 的 schedule 方法和 Promise 共同实现的</strong></p>
<ul>
<li>schedule 设置了一个定时任务，延迟<code>connectTimeoutMillis</code>秒后执行该方法</li>
<li>如果指定时间内没有建立连接，则会执行其中的任务<ul>
<li>任务负责创建 <code>ConnectTimeoutException</code> 异常，并将异常通过 Pormise 传给主线程并抛出</li>
</ul>
</li>
</ul>
<h3 id="SO-BACKLOG"><a href="#SO-BACKLOG" class="headerlink" title="SO_BACKLOG"></a>SO_BACKLOG</h3><p>该参数是 <strong>ServerSocketChannel</strong> 的参数</p>
<h4 id="三次握手与连接队列"><a href="#三次握手与连接队列" class="headerlink" title="三次握手与连接队列"></a>三次握手与连接队列</h4><p>第一次握手时，因为客户端与服务器之间的连接还未完全建立，连接会被放入<strong>半连接队列</strong>中</p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130120204.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<p>当完成三次握手以后，连接会被放入<strong>全连接队列中</strong></p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130120805.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<p>服务器处理Accept事件是在TCP三次握手，也就是建立连接之后。服务器会从全连接队列中获取连接并进行处理</p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130120227.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<p>在 linux 2.2 之前，backlog 大小包括了两个队列的大小，<strong>在 linux 2.2 之后，分别用下面两个参数来控制</strong></p>
<ul>
<li>半连接队列 - sync queue<ul>
<li>大小通过 /proc/sys/net/ipv4/tcp_max_syn_backlog 指定，在 <code>syncookies</code> 启用的情况下，逻辑上没有最大值限制，这个设置便被忽略</li>
</ul>
</li>
<li>全连接队列 - accept queue<ul>
<li>其大小通过 /proc/sys/net/core/somaxconn 指定，在使用 listen 函数时，<strong>内核会根据传入的 backlog 参数与系统参数，取二者的较小值</strong></li>
<li>如果 accpet queue 队列满了，server 将发送一个拒绝连接的错误信息到 client</li>
</ul>
</li>
</ul>
<h4 id="作用"><a href="#作用" class="headerlink" title="作用"></a>作用</h4><p>在Netty中，<code>SO_BACKLOG</code>主要用于设置全连接队列的大小。<strong>当处理Accept的速率小于连接建立的速率时，全连接队列中堆积的连接数大于<code>SO_BACKLOG</code>设置的值是，便会抛出异常</strong></p>
<p><strong>设置方式如下</strong></p>
<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs gradle"><span class="hljs-comment">// 设置全连接队列，大小为2</span><br><span class="hljs-keyword">new</span> ServerBootstrap().option(ChannelOption.SO_BACKLOG, <span class="hljs-number">2</span>);<span class="hljs-keyword">Copy</span><br></code></pre></td></tr></table></figure>

<h4 id="默认值"><a href="#默认值" class="headerlink" title="默认值"></a>默认值</h4><p>backlog参数在<code>NioSocketChannel.doBind</code>方法被使用</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">@Override<br>protected void <span class="hljs-keyword">do</span><span class="hljs-constructor">Bind(SocketAddress <span class="hljs-params">localAddress</span>)</span> throws Exception &#123;<br>    <span class="hljs-keyword">if</span> (<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">PlatformDependent</span>.</span></span>java<span class="hljs-constructor">Version()</span> &gt;= <span class="hljs-number">7</span>) &#123;<br>        java<span class="hljs-constructor">Channel()</span>.bind(localAddress, config.get<span class="hljs-constructor">Backlog()</span>);<br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>        java<span class="hljs-constructor">Channel()</span>.socket<span class="hljs-literal">()</span>.bind(localAddress, config.get<span class="hljs-constructor">Backlog()</span>);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>其中backlog被保存在了<code>DefaultServerSocketChannelConfig</code>配置类中</p>
<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs gradle"><span class="hljs-keyword">private</span> <span class="hljs-keyword">volatile</span> <span class="hljs-keyword">int</span> backlog = NetUtil.SOMAXCONN;<span class="hljs-keyword">Copy</span><br></code></pre></td></tr></table></figure>

<p>具体的赋值操作如下</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">SOMAXCONN = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">AccessController</span>.</span></span><span class="hljs-keyword">do</span><span class="hljs-constructor">Privileged(<span class="hljs-params">new</span> PrivilegedAction&lt;Integer&gt;()</span> &#123;<br>    @Override<br>    public Integer run<span class="hljs-literal">()</span> &#123;<br>        <span class="hljs-comment">// Determine the default somaxconn (server socket backlog) value of the platform.</span><br>        <span class="hljs-comment">// The known defaults:</span><br>        <span class="hljs-comment">// - Windows NT Server 4.0+: 200</span><br>        <span class="hljs-comment">// - Linux and Mac OS X: 128</span><br>        <span class="hljs-built_in">int</span> somaxconn = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">PlatformDependent</span>.</span></span>is<span class="hljs-constructor">Windows()</span> ? <span class="hljs-number">200</span> : <span class="hljs-number">128</span>;<br>        File file = <span class="hljs-keyword">new</span> <span class="hljs-constructor">File(<span class="hljs-string">&quot;/proc/sys/net/core/somaxconn&quot;</span>)</span>;<br>        BufferedReader <span class="hljs-keyword">in</span> = null;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">// file.exists() may throw a SecurityException if a SecurityManager is used, so execute it in the</span><br>            <span class="hljs-comment">// try / catch block.</span><br>            <span class="hljs-comment">// See https://github.com/netty/netty/issues/4936</span><br>            <span class="hljs-keyword">if</span> (file.exists<span class="hljs-literal">()</span>) &#123;<br>                <span class="hljs-keyword">in</span> = <span class="hljs-keyword">new</span> <span class="hljs-constructor">BufferedReader(<span class="hljs-params">new</span> FileReader(<span class="hljs-params">file</span>)</span>);<br>                <span class="hljs-comment">// 将somaxconn设置为Linux配置文件中设置的值</span><br>                somaxconn = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Integer</span>.</span></span>parse<span class="hljs-constructor">Int(<span class="hljs-params">in</span>.<span class="hljs-params">readLine</span>()</span>);<br>                <span class="hljs-keyword">if</span> (logger.is<span class="hljs-constructor">DebugEnabled()</span>) &#123;<br>                    logger.debug(<span class="hljs-string">&quot;&#123;&#125;: &#123;&#125;&quot;</span>, file, somaxconn);<br>                &#125;<br>            &#125; <span class="hljs-keyword">else</span> &#123;<span class="hljs-operator"></span><br><span class="hljs-operator">                ...</span><br><span class="hljs-operator">            </span>&#125;<span class="hljs-operator"></span><br><span class="hljs-operator">            ...</span><br><span class="hljs-operator">        </span>&#125;  <br>        <span class="hljs-comment">// 返回backlog的值</span><br>        return somaxconn;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<ul>
<li><p>backlog的值会根据操作系统的不同，来</p>
<p>选择不同的默认值</p>
<ul>
<li>Windows 200</li>
<li>Linux/Mac OS 128</li>
</ul>
</li>
<li><p><strong>如果配置文件<code>/proc/sys/net/core/somaxconn</code>存在</strong>，会读取配置文件中的值，并将backlog的值设置为配置文件中指定的</p>
</li>
</ul>
<h3 id="TCP-NODELAY"><a href="#TCP-NODELAY" class="headerlink" title="TCP_NODELAY"></a>TCP_NODELAY</h3><ul>
<li>属于 <strong>SocketChannal</strong> 参数</li>
<li>因为 Nagle 算法，数据包会堆积到一定的数量后一起发送，这就<strong>可能导致数据的发送存在一定的延时</strong></li>
<li><strong>该参数默认为false</strong>，如果不希望的发送被延时，则需要将该值设置为true</li>
</ul>
<h3 id="SO-SNDBUF-amp-SO-RCVBUF"><a href="#SO-SNDBUF-amp-SO-RCVBUF" class="headerlink" title="SO_SNDBUF &amp; SO_RCVBUF"></a>SO_SNDBUF &amp; SO_RCVBUF</h3><ul>
<li>SO_SNDBUF 属于 <strong>SocketChannal</strong> 参数</li>
<li>SO_RCVBUF <strong>既可用于 SocketChannal 参数，也可以用于 ServerSocketChannal 参数</strong>（建议设置到 ServerSocketChannal 上）</li>
<li>该参数用于<strong>指定接收方与发送方的滑动窗口大小</strong></li>
</ul>
<h3 id="ALLOCATOR"><a href="#ALLOCATOR" class="headerlink" title="ALLOCATOR"></a>ALLOCATOR</h3><ul>
<li>属于 <strong>SocketChannal</strong> 参数</li>
<li>用来配置 ByteBuf 是池化还是非池化，是直接内存还是堆内存</li>
</ul>
<h4 id="使用-1"><a href="#使用-1" class="headerlink" title="使用"></a>使用</h4><figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs haxe"><span class="hljs-comment">// 选择ALLOCATOR参数，设置SocketChannel中分配的ByteBuf类型</span><br><span class="hljs-comment">// 第二个参数需要传入一个ByteBufAllocator，用于指定生成的 ByteBuf 的类型</span><br><span class="hljs-keyword">new</span> <span class="hljs-type">ServerBootstrap</span>().childOption(ChannelOption.ALLOCATOR, <span class="hljs-keyword">new</span> <span class="hljs-type">PooledByteBufAllocator</span>());Copy<br></code></pre></td></tr></table></figure>

<p><strong>ByteBufAllocator类型</strong></p>
<ul>
<li><p>池化并使用直接内存</p>
<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs gradle"><span class="hljs-comment">// true表示使用直接内存</span><br><span class="hljs-keyword">new</span> PooledByteBufAllocator(<span class="hljs-keyword">true</span>);<span class="hljs-keyword">Copy</span><br></code></pre></td></tr></table></figure></li>
<li><p>池化并使用堆内存</p>
<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs gradle"><span class="hljs-comment">// false表示使用堆内存</span><br><span class="hljs-keyword">new</span> PooledByteBufAllocator(<span class="hljs-keyword">false</span>);<span class="hljs-keyword">Copy</span><br></code></pre></td></tr></table></figure></li>
<li><p>非池化并使用直接内存</p>
<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs gradle"><span class="hljs-comment">// ture表示使用直接内存</span><br><span class="hljs-keyword">new</span> UnpooledByteBufAllocator(<span class="hljs-keyword">true</span>);<span class="hljs-keyword">Copy</span><br></code></pre></td></tr></table></figure></li>
<li><p>非池化并使用堆内存</p>
<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs gradle"><span class="hljs-comment">// false表示使用堆内存</span><br><span class="hljs-keyword">new</span> UnpooledByteBufAllocator(<span class="hljs-keyword">false</span>);<span class="hljs-keyword">Copy</span><br></code></pre></td></tr></table></figure></li>
</ul>
<h3 id="RCVBUF-ALLOCATOR"><a href="#RCVBUF-ALLOCATOR" class="headerlink" title="RCVBUF_ALLOCATOR"></a>RCVBUF_ALLOCATOR</h3><ul>
<li>属于 <strong>SocketChannal</strong> 参数</li>
<li><strong>控制 Netty 接收缓冲区大小</strong></li>
<li>负责入站数据的分配，决定入站缓冲区的大小（并可动态调整），<strong>统一采用 direct 直接内存</strong>，具体池化还是非池化由 allocator 决定</li>
</ul>
<h2 id="3、RPC框架"><a href="#3、RPC框架" class="headerlink" title="3、RPC框架"></a>3、RPC框架</h2><h3 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h3><p>在聊天室代码的基础上进行一定的改进</p>
<p><strong>Message</strong>中添加如下代码</p>
<figure class="highlight axapta"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs axapta"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Message</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Serializable</span> </span>&#123;<br><br>    ...<br><br>    <span class="hljs-comment">// 添加RPC消息类型</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-built_in">int</span> RPC_MESSAGE_TYPE_REQUEST = <span class="hljs-number">101</span>;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-built_in">int</span>  RPC_MESSAGE_TYPE_RESPONSE = <span class="hljs-number">102</span>;<br><br>    <span class="hljs-keyword">static</span> &#123;<br>        <span class="hljs-comment">// 将消息类型放入消息类对象Map中</span><br>        messageClasses.put(RPC_MESSAGE_TYPE_REQUEST, RpcRequestMessage.<span class="hljs-keyword">class</span>);<br>        messageClasses.put(RPC_MESSAGE_TYPE_RESPONSE, RpcResponseMessage.<span class="hljs-keyword">class</span>);<br>    &#125;<br><br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>RPC请求消息</strong></p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RpcRequestMessage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Message</span> </span>&#123;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 调用的接口全限定名，服务端根据它找到实现</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-built_in">String</span> interfaceName;<br>    <br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 调用接口中的方法名</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-built_in">String</span> methodName;<br>    <br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 方法返回类型</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> Class&lt;?&gt; returnType;<br>    <br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 方法参数类型数组</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> Class[] parameterTypes;<br>    <br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 方法参数值数组</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-built_in">Object</span>[] parameterValue;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-title">RpcRequestMessage</span>(<span class="hljs-params">int sequenceId, <span class="hljs-built_in">String</span> interfaceName, <span class="hljs-built_in">String</span> methodName, Class&lt;?&gt; returnType, Class[] parameterTypes, <span class="hljs-built_in">Object</span>[] parameterValue</span>)</span> &#123;<br>        <span class="hljs-built_in">super</span>.setSequenceId(sequenceId);<br>        <span class="hljs-built_in">this</span>.interfaceName = interfaceName;<br>        <span class="hljs-built_in">this</span>.methodName = methodName;<br>        <span class="hljs-built_in">this</span>.returnType = returnType;<br>        <span class="hljs-built_in">this</span>.parameterTypes = parameterTypes;<br>        <span class="hljs-built_in">this</span>.parameterValue = parameterValue;<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> int <span class="hljs-function"><span class="hljs-title">getMessageType</span>(<span class="hljs-params"></span>)</span> &#123;<br>        <span class="hljs-keyword">return</span> RPC_MESSAGE_TYPE_REQUEST;<br>    &#125;<br>    <br>     <span class="hljs-keyword">public</span> <span class="hljs-built_in">String</span> <span class="hljs-function"><span class="hljs-title">getInterfaceName</span>(<span class="hljs-params"></span>)</span> &#123;<br>        <span class="hljs-keyword">return</span> interfaceName;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-built_in">String</span> <span class="hljs-function"><span class="hljs-title">getMethodName</span>(<span class="hljs-params"></span>)</span> &#123;<br>        <span class="hljs-keyword">return</span> methodName;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> Class&lt;?&gt; <span class="hljs-function"><span class="hljs-title">getReturnType</span>(<span class="hljs-params"></span>)</span> &#123;<br>        <span class="hljs-keyword">return</span> returnType;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> Class[] <span class="hljs-function"><span class="hljs-title">getParameterTypes</span>(<span class="hljs-params"></span>)</span> &#123;<br>        <span class="hljs-keyword">return</span> parameterTypes;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-built_in">Object</span>[] <span class="hljs-function"><span class="hljs-title">getParameterValue</span>(<span class="hljs-params"></span>)</span> &#123;<br>        <span class="hljs-keyword">return</span> parameterValue;<br>    &#125;<br>    <br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-built_in">String</span> <span class="hljs-function"><span class="hljs-title">toString</span>(<span class="hljs-params"></span>)</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;RpcRequestMessage&#123;&quot;</span> +<br>                <span class="hljs-string">&quot;interfaceName=&#x27;&quot;</span> + interfaceName + <span class="hljs-string">&#x27;\&#x27;&#x27;</span> +<br>                <span class="hljs-string">&quot;, methodName=&#x27;&quot;</span> + methodName + <span class="hljs-string">&#x27;\&#x27;&#x27;</span> +<br>                <span class="hljs-string">&quot;, returnType=&quot;</span> + returnType +<br>                <span class="hljs-string">&quot;, parameterTypes=&quot;</span> + Arrays.toString(parameterTypes) +<br>                <span class="hljs-string">&quot;, parameterValue=&quot;</span> + Arrays.toString(parameterValue) +<br>                <span class="hljs-string">&#x27;&#125;&#x27;</span>;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>想要远程调用一个方法，必须知道以<strong>下五个信息</strong></p>
<ul>
<li>方法所在的全限定类名</li>
<li>方法名</li>
<li>方法返回值类型</li>
<li>方法参数类型</li>
<li>方法参数值</li>
</ul>
<p><strong>RPC响应消息</strong></p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RpcResponseMessage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Message</span> </span>&#123;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 返回值</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-built_in">Object</span> returnValue;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 异常值</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> Exception exceptionValue;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> int <span class="hljs-function"><span class="hljs-title">getMessageType</span>(<span class="hljs-params"></span>)</span> &#123;<br>        <span class="hljs-keyword">return</span> RPC_MESSAGE_TYPE_RESPONSE;<br>    &#125;<br>    <br>    <br>    <span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> <span class="hljs-function"><span class="hljs-title">setReturnValue</span>(<span class="hljs-params"><span class="hljs-built_in">Object</span> returnValue</span>)</span> &#123;<br>        <span class="hljs-built_in">this</span>.returnValue = returnValue;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> <span class="hljs-function"><span class="hljs-title">setExceptionValue</span>(<span class="hljs-params">Exception exceptionValue</span>)</span> &#123;<br>        <span class="hljs-built_in">this</span>.exceptionValue = exceptionValue;<br>    &#125;<br>    <br>     <span class="hljs-keyword">public</span> <span class="hljs-built_in">Object</span> <span class="hljs-function"><span class="hljs-title">getReturnValue</span>(<span class="hljs-params"></span>)</span> &#123;<br>        <span class="hljs-keyword">return</span> returnValue;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> Exception <span class="hljs-function"><span class="hljs-title">getExceptionValue</span>(<span class="hljs-params"></span>)</span> &#123;<br>        <span class="hljs-keyword">return</span> exceptionValue;<br>    &#125;<br>    <br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-built_in">String</span> <span class="hljs-function"><span class="hljs-title">toString</span>(<span class="hljs-params"></span>)</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;RpcResponseMessage&#123;&quot;</span> +<br>                <span class="hljs-string">&quot;returnValue=&quot;</span> + returnValue +<br>                <span class="hljs-string">&quot;, exceptionValue=&quot;</span> + exceptionValue +<br>                <span class="hljs-string">&#x27;&#125;&#x27;</span>;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>响应消息中只需要获取<strong>返回结果和异常值</strong></p>
<p><strong>服务器</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> RPCServer &#123;<br>    public static void main(String<span class="hljs-literal">[]</span> args) &#123;<br>        NioEventLoopGroup boss = <span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup()</span>;<br>        NioEventLoopGroup worker = <span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup()</span>;<br>        LoggingHandler loggingHandler = <span class="hljs-keyword">new</span> <span class="hljs-constructor">LoggingHandler(LogLevel.DEBUG)</span>;<br>        MessageSharableCodec messageSharableCodec = <span class="hljs-keyword">new</span> <span class="hljs-constructor">MessageSharableCodec()</span>;<br><br>        <span class="hljs-comment">// PRC 请求消息处理器</span><br>        RpcRequestMessageHandler rpcRequestMessageHandler = <span class="hljs-keyword">new</span> <span class="hljs-constructor">RpcRequestMessageHandler()</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            ServerBootstrap serverBootstrap = <span class="hljs-keyword">new</span> <span class="hljs-constructor">ServerBootstrap()</span>;<br>            serverBootstrap.channel(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">NioServerSocketChannel</span>.</span></span><span class="hljs-keyword">class</span>);<br>            serverBootstrap.group(boss, worker);<br>            serverBootstrap.child<span class="hljs-constructor">Handler(<span class="hljs-params">new</span> ChannelInitializer&lt;SocketChannel&gt;()</span> &#123;<br>                @Override<br>                protected void init<span class="hljs-constructor">Channel(SocketChannel <span class="hljs-params">ch</span>)</span> throws Exception &#123;<br>                    ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> ProtocolFrameDecoder()</span>);<br>                    ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">loggingHandler</span>)</span>;<br>                    ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">messageSharableCodec</span>)</span>;<br>                    ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">rpcRequestMessageHandler</span>)</span>;<br>                &#125;<br>            &#125;);<br>            Channel channel = serverBootstrap.bind(<span class="hljs-number">8080</span>).sync<span class="hljs-literal">()</span>.channel<span class="hljs-literal">()</span>;<br>            channel.close<span class="hljs-constructor">Future()</span>.sync<span class="hljs-literal">()</span>;<br>        &#125; catch (InterruptedException e) &#123;<br>            e.print<span class="hljs-constructor">StackTrace()</span>;<br>        &#125; finally &#123;<br>            boss.shutdown<span class="hljs-constructor">Gracefully()</span>;<br>            worker.shutdown<span class="hljs-constructor">Gracefully()</span>;<br>        &#125;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>服务器中添加了<strong>处理RPCRequest消息的handler</strong></p>
<p><strong>客户端</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public <span class="hljs-keyword">class</span> RPCClient &#123;<br>    public static void main(String<span class="hljs-literal">[]</span> args) &#123;<br>        NioEventLoopGroup group = <span class="hljs-keyword">new</span> <span class="hljs-constructor">NioEventLoopGroup()</span>;<br>        LoggingHandler loggingHandler = <span class="hljs-keyword">new</span> <span class="hljs-constructor">LoggingHandler(LogLevel.DEBUG)</span>;<br>        MessageSharableCodec messageSharableCodec = <span class="hljs-keyword">new</span> <span class="hljs-constructor">MessageSharableCodec()</span>;<br><br>        <span class="hljs-comment">// PRC 请求消息处理器</span><br>        RpcResponseMessageHandler rpcResponseMessageHandler = <span class="hljs-keyword">new</span> <span class="hljs-constructor">RpcResponseMessageHandler()</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            Bootstrap bootstrap = <span class="hljs-keyword">new</span> <span class="hljs-constructor">Bootstrap()</span>;<br>            bootstrap.channel(<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">NioSocketChannel</span>.</span></span><span class="hljs-keyword">class</span>);<br>            bootstrap.group(group);<br>            bootstrap.handler(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;<span class="hljs-literal">()</span> &#123;<br>                @Override<br>                protected void init<span class="hljs-constructor">Channel(SocketChannel <span class="hljs-params">ch</span>)</span> throws Exception &#123;<br>                    ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">new</span> ProtocolFrameDecoder()</span>);<br>                    ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">loggingHandler</span>)</span>;<br>                    ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">messageSharableCodec</span>)</span>;<br>                    ch.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">rpcResponseMessageHandler</span>)</span>;<br>                &#125;<br>            &#125;);<br>            Channel channel = bootstrap.connect(<span class="hljs-keyword">new</span> <span class="hljs-constructor">InetSocketAddress(<span class="hljs-string">&quot;localhost&quot;</span>, 8080)</span>).sync<span class="hljs-literal">()</span>.channel<span class="hljs-literal">()</span>;<br>            channel.close<span class="hljs-constructor">Future()</span>.sync<span class="hljs-literal">()</span>;<br>        &#125; catch (InterruptedException e) &#123;<br>            e.print<span class="hljs-constructor">StackTrace()</span>;<br>        &#125; finally &#123;<br>            group.shutdown<span class="hljs-constructor">Gracefully()</span>;<br>        &#125;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>通过接口Class获取实例对象的<strong>Factory</strong></p>
<figure class="highlight processing"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs processing"><span class="hljs-keyword">public</span> class ServicesFactory &#123;<br>    <span class="hljs-keyword">static</span> <span class="hljs-keyword">HashMap</span>&lt;Class&lt;?&gt;, <span class="hljs-keyword">Object</span>&gt; <span class="hljs-built_in">map</span> = <span class="hljs-keyword">new</span> <span class="hljs-keyword">HashMap</span>&lt;&gt;(<span class="hljs-number">16</span>);<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">Object</span> getInstance(Class&lt;?&gt; interfaceClass) <span class="hljs-keyword">throws</span> ClassNotFoundException, IllegalAccessException, InstantiationException &#123;<br>        <span class="hljs-comment">// 根据Class创建实例</span><br>        <span class="hljs-keyword">try</span> &#123;<br>            Class&lt;?&gt; clazz = Class.forName(<span class="hljs-string">&quot;cn.nyimac.study.day8.server.service.HelloService&quot;</span>);<br>            <span class="hljs-keyword">Object</span> instance = Class.forName(<span class="hljs-string">&quot;cn.nyimac.study.day8.server.service.HelloServiceImpl&quot;</span>).newInstance();<br>           <br>            <span class="hljs-comment">// 放入 InterfaceClass -&gt; InstanceObject 的映射</span><br>            <span class="hljs-built_in">map</span>.put(clazz, instance);<br>        &#125; <span class="hljs-keyword">catch</span> (ClassNotFoundException | InstantiationException | IllegalAccessException e) &#123;<br>            e.printStackTrace();<br>        &#125;  <br>        <span class="hljs-keyword">return</span> <span class="hljs-built_in">map</span>.<span class="hljs-built_in">get</span>(interfaceClass);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h3 id="RpcRequestMessageHandler"><a href="#RpcRequestMessageHandler" class="headerlink" title="RpcRequestMessageHandler"></a>RpcRequestMessageHandler</h3><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">@ChannelHandler.Sharable<br>public <span class="hljs-keyword">class</span> RpcRequestMessageHandler extends SimpleChannelInboundHandler&lt;RpcRequestMessage&gt; &#123;<br>    @Override<br>    protected void channel<span class="hljs-constructor">Read0(ChannelHandlerContext <span class="hljs-params">ctx</span>, RpcRequestMessage <span class="hljs-params">rpcMessage</span>)</span> &#123;<br>        RpcResponseMessage rpcResponseMessage = <span class="hljs-keyword">new</span> <span class="hljs-constructor">RpcResponseMessage()</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">// 设置返回值的属性</span><br>            rpcResponseMessage.set<span class="hljs-constructor">SequenceId(<span class="hljs-params">rpcMessage</span>.<span class="hljs-params">getSequenceId</span>()</span>);<br>            <span class="hljs-comment">// 返回一个实例</span><br>            HelloService service = (HelloService) <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">ServicesFactory</span>.</span></span>get<span class="hljs-constructor">Instance(Class.<span class="hljs-params">forName</span>(<span class="hljs-params">rpcMessage</span>.<span class="hljs-params">getInterfaceName</span>()</span>));<br>            <br>            <span class="hljs-comment">// 通过反射调用方法，并获取返回值</span><br>            Method <span class="hljs-keyword">method</span> = service.get<span class="hljs-constructor">Class()</span>.get<span class="hljs-constructor">Method(<span class="hljs-params">rpcMessage</span>.<span class="hljs-params">getMethodName</span>()</span>, rpcMessage.get<span class="hljs-constructor">ParameterTypes()</span>);<br>            <span class="hljs-comment">// 获得返回值</span><br>            Object invoke = <span class="hljs-keyword">method</span>.invoke(service, rpcMessage.get<span class="hljs-constructor">ParameterValue()</span>);<br>            <span class="hljs-comment">// 设置返回值</span><br>            rpcResponseMessage.set<span class="hljs-constructor">ReturnValue(<span class="hljs-params">invoke</span>)</span>;<br>        &#125; catch (Exception e) &#123;<br>            e.print<span class="hljs-constructor">StackTrace()</span>;<br>            <span class="hljs-comment">// 设置异常</span><br>            rpcResponseMessage.set<span class="hljs-constructor">ExceptionValue(<span class="hljs-params">e</span>)</span>;<br>        &#125;<br>    &#125;<br>    <span class="hljs-comment">// 向channel中写入Message</span><br>    ctx.write<span class="hljs-constructor">AndFlush(<span class="hljs-params">rpcResponseMessage</span>)</span>;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>远程调用方法主要是通过反射实现的，大致步骤如下</p>
<ul>
<li>通过<strong>请求消息传入被调入方法的各个参数</strong></li>
<li>通过<strong>全限定接口名，在map中查询到对应的类并实例化对象</strong></li>
<li>通过反射获取Method，并调用其invoke方法的<strong>返回值，并放入响应消息中</strong></li>
<li>若有<strong>异常需要捕获，并放入响应消息中</strong></li>
</ul>
<h3 id="RpcResponseMessageHandler"><a href="#RpcResponseMessageHandler" class="headerlink" title="RpcResponseMessageHandler"></a>RpcResponseMessageHandler</h3><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs scala"><span class="hljs-meta">@ChannelHandler</span>.<span class="hljs-type">Sharable</span><br>public <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RpcResponseMessageHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SimpleChannelInboundHandler&lt;RpcResponseMessage&gt;</span> </span>&#123;<br>    static <span class="hljs-keyword">final</span> <span class="hljs-type">Logger</span> log = <span class="hljs-type">LoggerFactory</span>.getLogger(<span class="hljs-type">ChatServer</span>.<span class="hljs-keyword">class</span>);<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> void channelRead0(<span class="hljs-type">ChannelHandlerContext</span> ctx, <span class="hljs-type">RpcResponseMessage</span> msg) <span class="hljs-keyword">throws</span> <span class="hljs-type">Exception</span> &#123;<br>        log.debug(<span class="hljs-string">&quot;&#123;&#125;&quot;</span>, msg);<br>        <span class="hljs-type">System</span>.out.println((<span class="hljs-type">String</span>)msg.getReturnValue());<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h3 id="客户端发送消息"><a href="#客户端发送消息" class="headerlink" title="客户端发送消息"></a>客户端发送消息</h3><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RPCClient</span> </span>&#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-keyword">void</span> main(<span class="hljs-keyword">String</span>[] args) &#123;<br>		...<br>           <br>        <span class="hljs-comment">// 创建请求并发送</span><br>		RpcRequestMessage message = <span class="hljs-keyword">new</span> RpcRequestMessage(<span class="hljs-number">1</span>,<br>               <span class="hljs-string">&quot;cn.nyimac.study.day8.server.service.HelloService&quot;</span>,<br>               <span class="hljs-string">&quot;sayHello&quot;</span>,<br>               <span class="hljs-keyword">String</span>.<span class="hljs-keyword">class</span>,<br>               <span class="hljs-keyword">new</span> <span class="hljs-class"><span class="hljs-keyword">Class</span>[]</span>&#123;<span class="hljs-keyword">String</span>.<span class="hljs-keyword">class</span>&#125;,<br>               <span class="hljs-keyword">new</span> <span class="hljs-keyword">Object</span>[]&#123;<span class="hljs-string">&quot;Nyima&quot;</span>&#125;);<br>		<br>        channel.writeAndFlush(message);   <br>            <br>        ...    <br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>运行结果</strong></p>
<p>客户端</p>
<figure class="highlight basic"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs basic"><span class="hljs-symbol">1606 </span>[nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.server.ChatServer  - RpcResponseMessage&#123;returnValue=你好，Nyima, exceptionValue=null&#125; <br></code></pre></td></tr></table></figure>

<h3 id="改进客户端"><a href="#改进客户端" class="headerlink" title="改进客户端"></a>改进客户端</h3><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre></td><td class="code"><pre><code class="hljs csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">RPCClientManager</span> &#123;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 产生SequenceId</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> AtomicInteger sequenceId = <span class="hljs-keyword">new</span> AtomicInteger(<span class="hljs-number">0</span>);<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">volatile</span> Channel channel = <span class="hljs-literal">null</span>;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> final Object <span class="hljs-keyword">lock</span> = <span class="hljs-keyword">new</span> Object();<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span>(<span class="hljs-params">String[] args</span>)</span> &#123;<br>        <span class="hljs-comment">// 创建代理对象</span><br>        HelloService service = (HelloService) getProxy(HelloService.<span class="hljs-keyword">class</span>);<br>        <span class="hljs-comment">// 通过代理对象执行方法</span><br>        System.<span class="hljs-keyword">out</span>.println(service.sayHello(<span class="hljs-string">&quot;Nyima&quot;</span>));<br>        System.<span class="hljs-keyword">out</span>.println(service.sayHello(<span class="hljs-string">&quot;Hulu&quot;</span>));<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 单例模式创建Channel</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Channel <span class="hljs-title">getChannel</span>(<span class="hljs-params"></span>)</span> &#123;<br>        <span class="hljs-keyword">if</span> (channel == <span class="hljs-literal">null</span>) &#123;<br>            synchronized (<span class="hljs-keyword">lock</span>) &#123;<br>                <span class="hljs-keyword">if</span> (channel == <span class="hljs-literal">null</span>) &#123;<br>                    <span class="hljs-keyword">init</span>();<br>                &#125;<br>            &#125;<br>        &#125;<br>        <span class="hljs-keyword">return</span> channel;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 使用代理模式，帮助我们创建请求消息并发送</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Object <span class="hljs-title">getProxy</span>(<span class="hljs-params">Class&lt;?&gt; serviceClass</span>)</span> &#123;<br>        Class&lt;?&gt;[] classes = <span class="hljs-keyword">new</span> Class&lt;?&gt;[]&#123;serviceClass&#125;;<br>        <span class="hljs-comment">// 使用JDK代理，创建代理对象</span><br>        Object o = Proxy.newProxyInstance(serviceClass.getClassLoader(), classes, <span class="hljs-keyword">new</span> InvocationHandler() &#123;<br>            @Override<br>            <span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">invoke</span>(<span class="hljs-params">Object proxy, Method method, Object[] args</span>) throws Throwable</span> &#123;<br>                <span class="hljs-comment">// 创建请求消息</span><br>                <span class="hljs-built_in">int</span> id = sequenceId.getAndIncrement();<br>                RpcRequestMessage message = <span class="hljs-keyword">new</span> RpcRequestMessage(id, serviceClass.getName(),<br>                        method.getName(), method.getReturnType(),<br>                        method.getParameterTypes(),<br>                        args);<br>                <span class="hljs-comment">// 发送消息</span><br>                getChannel().writeAndFlush(message);<br><br>                <span class="hljs-comment">// 创建Promise，用于获取NIO线程中的返回结果，获取的过程是异步的</span><br>                DefaultPromise&lt;Object&gt; promise = <span class="hljs-keyword">new</span> DefaultPromise&lt;&gt;(getChannel().eventLoop());<br>                <span class="hljs-comment">// 将Promise放入Map中</span><br>                RpcResponseMessageHandler.promiseMap.put(id, promise);<br>                <span class="hljs-comment">// 等待被放入Promise中结果</span><br>                promise.<span class="hljs-keyword">await</span>();<br>                <span class="hljs-keyword">if</span> (promise.isSuccess()) &#123;<br>                    <span class="hljs-comment">// 调用方法成功，返回方法执行结果</span><br>                    <span class="hljs-keyword">return</span> promise.getNow();<br>                &#125; <span class="hljs-keyword">else</span> &#123;<br>                    <span class="hljs-comment">// 调用方法失败，抛出异常</span><br>                    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(promise.cause());<br>                &#125;<br>            &#125;<br>        &#125;);<br>        <span class="hljs-keyword">return</span> o;<br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span>(<span class="hljs-params"></span>)</span> &#123;<br>        NioEventLoopGroup <span class="hljs-keyword">group</span> = <span class="hljs-keyword">new</span> NioEventLoopGroup();<br>        LoggingHandler loggingHandler = <span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG);<br>        MessageSharableCodec messageSharableCodec = <span class="hljs-keyword">new</span> MessageSharableCodec();<br><br>        <span class="hljs-comment">// PRC 请求消息处理器</span><br>        RpcResponseMessageHandler rpcResponseMessageHandler = <span class="hljs-keyword">new</span> RpcResponseMessageHandler();<br><br>        Bootstrap bootstrap = <span class="hljs-keyword">new</span> Bootstrap();<br>        bootstrap.channel(NioSocketChannel.<span class="hljs-keyword">class</span>);<br>        bootstrap.<span class="hljs-keyword">group</span>(<span class="hljs-keyword">group</span>);<br>        bootstrap.handler(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;() &#123;<br>            @Override<br>            <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span>(<span class="hljs-params">SocketChannel ch</span>) throws Exception</span> &#123;<br>                ch.pipeline().addLast(<span class="hljs-keyword">new</span> ProtocolFrameDecoder());<br>                ch.pipeline().addLast(loggingHandler);<br>                ch.pipeline().addLast(messageSharableCodec);<br>                ch.pipeline().addLast(rpcResponseMessageHandler);<br>            &#125;<br>        &#125;);<br>        <span class="hljs-keyword">try</span> &#123;<br>            channel = bootstrap.connect(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-string">&quot;localhost&quot;</span>, <span class="hljs-number">8080</span>)).sync().channel();<br>            <span class="hljs-comment">// 异步关闭 group，避免Channel被阻塞</span><br>            channel.closeFuture().addListener(future -&gt; &#123;<br>                <span class="hljs-keyword">group</span>.shutdownGracefully();<br>            &#125;);<br>        &#125; catch (InterruptedException e) &#123;<br>            e.printStackTrace();<br>        &#125;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>获得Channel</strong></p>
<ul>
<li>建立连接，获取Channel的操作被封装到了<code>init</code>方法中，当连接断开时，通过<code>addListener</code>方<strong>法异步关闭group</strong></li>
<li>通过<strong>单例模式</strong>创建与获取Channel</li>
</ul>
<p><strong>远程调用方法</strong></p>
<ul>
<li>为了让方法的调用变得简洁明了，将<code>RpcRequestMessage</code>的<strong>创建与发送过程通过JDK的动态代理来完成</strong></li>
<li>通过返回的代理对象调用方法即可，<strong>方法参数为被调用方法接口的Class类</strong></li>
</ul>
<p><strong>远程调用方法返回值获取</strong></p>
<ul>
<li><p>调用方法的是主线程，处理返回结果的是NIO线程（RpcResponseMessageHandler）。<strong>要在不同线程中进行返回值的传递，需要用到Promise</strong></p>
</li>
<li><p>在<code>RpcResponseMessageHandler</code>中创建一个Map</p>
<ul>
<li>Key为<strong>SequenceId</strong></li>
<li>Value为对应的<strong>Promise</strong></li>
</ul>
</li>
<li><p><strong>主线程</strong>的代理类将RpcResponseMessage发送给服务器后，需要创建Promise对象，并将其放入到RpcResponseMessageHandler的Map中。<strong>需要使用await等待结果被放入Promise中</strong>。获取结果后，根据结果类型（判断是否成功）来返回结果或抛出异常</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-comment">// 创建Promise，用于获取NIO线程中的返回结果，获取的过程是异步的</span><br>DefaultPromise&lt;Object&gt; promise = <span class="hljs-keyword">new</span> DefaultPromise&lt;&gt;(get<span class="hljs-constructor">Channel()</span>.event<span class="hljs-constructor">Loop()</span>);<br><span class="hljs-comment">// 将Promise放入Map中</span><br><span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">RpcResponseMessageHandler</span>.</span></span>promiseMap.put(id, promise);<br><span class="hljs-comment">// 等待被放入Promise中结果</span><br>promise.await<span class="hljs-literal">()</span>;<br><span class="hljs-keyword">if</span> (promise.is<span class="hljs-constructor">Success()</span>) &#123;<br>    <span class="hljs-comment">// 调用方法成功，返回方法执行结果</span><br>    return promise.get<span class="hljs-constructor">Now()</span>;<br>&#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-comment">// 调用方法失败，抛出异常</span><br>    throw <span class="hljs-keyword">new</span> <span class="hljs-constructor">RuntimeException(<span class="hljs-params">promise</span>.<span class="hljs-params">cause</span>()</span>);<br>&#125; <br></code></pre></td></tr></table></figure></li>
<li><p><strong>NIO线程</strong>负责通过SequenceId<strong>获取并移除（remove）</strong>对应的Promise，然后根据RpcResponseMessage中的结果，向Promise中放入不同的值</p>
<ul>
<li>如果<strong>没有异常信息</strong>（ExceptionValue），就调用<code>promise.setSuccess(returnValue)</code>放入方法返回值</li>
<li>如果<strong>有异常信息</strong>，就调用<code>promise.setFailure(exception)</code>放入异常信息</li>
</ul>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-comment">// 将返回结果放入对应的Promise中，并移除Map中的Promise</span><br>Promise&lt;Object&gt; promise = promiseMap.remove(msg.get<span class="hljs-constructor">SequenceId()</span>);<br>Object returnValue = msg.get<span class="hljs-constructor">ReturnValue()</span>;<br>Exception <span class="hljs-keyword">exception</span> = msg.get<span class="hljs-constructor">ExceptionValue()</span>;<br><span class="hljs-keyword">if</span> (promise != null) &#123;<br>    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">exception</span> != null) &#123;<br>        <span class="hljs-comment">// 返回结果中有异常信息</span><br>        promise.set<span class="hljs-constructor">Failure(<span class="hljs-params">exception</span>)</span>;<br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>        <span class="hljs-comment">// 方法正常执行，没有异常</span><br>        promise.set<span class="hljs-constructor">Success(<span class="hljs-params">returnValue</span>)</span>;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure></li>
</ul>
<h3 id="改进RpcResponseMessageHandler"><a href="#改进RpcResponseMessageHandler" class="headerlink" title="改进RpcResponseMessageHandler"></a>改进RpcResponseMessageHandler</h3><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs php">@ChannelHandler.Sharable<br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RpcResponseMessageHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SimpleChannelInboundHandler</span>&lt;<span class="hljs-title">RpcResponseMessage</span>&gt; </span>&#123;<br>    <span class="hljs-built_in">static</span> <span class="hljs-keyword">final</span> Logger log = LoggerFactory.getLogger(ChatServer.<span class="hljs-keyword">class</span>);<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 用于存放Promise的集合，Promise用于主线程与NIO线程之间传递返回值</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> Map&lt;<span class="hljs-keyword">Integer</span>, Promise&lt;<span class="hljs-keyword">Object</span>&gt;&gt; promiseMap = <span class="hljs-keyword">new</span> ConcurrentHashMap&lt;&gt;(<span class="hljs-number">16</span>);<br><br>    @Override<br>    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> channelRead0(ChannelHandlerContext ctx, RpcResponseMessage msg) throws <span class="hljs-built_in">Exception</span> &#123;<br>        <span class="hljs-comment">// 将返回结果放入对应的Promise中，并移除Map中的Promise</span><br>        Promise&lt;<span class="hljs-keyword">Object</span>&gt; promise = promiseMap.remove(msg.getSequenceId());<br>        <span class="hljs-keyword">Object</span> returnValue = msg.getReturnValue();<br>        <span class="hljs-built_in">Exception</span> <span class="hljs-built_in">exception</span> = msg.getExceptionValue();<br>        <span class="hljs-keyword">if</span> (promise != <span class="hljs-literal">null</span>) &#123;<br>            <span class="hljs-keyword">if</span> (<span class="hljs-built_in">exception</span> != <span class="hljs-literal">null</span>) &#123;<br>                <span class="hljs-comment">// 返回结果中有异常信息</span><br>                promise.setFailure(<span class="hljs-built_in">exception</span>);<br>            &#125; <span class="hljs-keyword">else</span> &#123;<br>                <span class="hljs-comment">// 方法正常执行，没有异常</span><br>                promise.setSuccess(returnValue);<br>            &#125;<br>        &#125;<br>        <span class="hljs-comment">// 拿到返回结果并打印</span><br>        log.debug(<span class="hljs-string">&quot;&#123;&#125;&quot;</span>, msg);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h1 id="六、源码"><a href="#六、源码" class="headerlink" title="六、源码"></a>六、源码</h1><h2 id="1、启动流程"><a href="#1、启动流程" class="headerlink" title="1、启动流程"></a>1、启动流程</h2><p>Netty启动流程可以简化成如下代码</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-comment">// netty 中使用 NioEventLoopGroup （简称 nio boss 线程）来封装线程和 selector</span><br>Selector selector = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Selector</span>.</span></span><span class="hljs-keyword">open</span><span class="hljs-literal">()</span>; <br><br><span class="hljs-comment">// 创建 NioServerSocketChannel，同时会初始化它关联的 handler，以及为原生 ssc 存储 config</span><br>NioServerSocketChannel attachment = <span class="hljs-keyword">new</span> <span class="hljs-constructor">NioServerSocketChannel()</span>;<br><br><span class="hljs-comment">// 创建 NioServerSocketChannel 时，创建了 java 原生的 ServerSocketChannel</span><br>ServerSocketChannel serverSocketChannel = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">ServerSocketChannel</span>.</span></span><span class="hljs-keyword">open</span><span class="hljs-literal">()</span>; <br>serverSocketChannel.configure<span class="hljs-constructor">Blocking(<span class="hljs-params">false</span>)</span>;<br><br><span class="hljs-comment">// 启动 nio boss 线程执行接下来的操作</span><br><br><span class="hljs-comment">//注册（仅关联 selector 和 NioServerSocketChannel），未关注事件</span><br>SelectionKey selectionKey = serverSocketChannel.register(selector, <span class="hljs-number">0</span>, attachment);<br><br><span class="hljs-comment">// head -&gt; 初始化器 -&gt; ServerBootstrapAcceptor -&gt; tail，初始化器是一次性的，只为添加 acceptor</span><br><br><span class="hljs-comment">// 绑定端口</span><br>serverSocketChannel.bind(<span class="hljs-keyword">new</span> <span class="hljs-constructor">InetSocketAddress(8080)</span>);<br><br><span class="hljs-comment">// 触发 channel active 事件，在 head 中关注 op_accept 事件</span><br>selectionKey.interest<span class="hljs-constructor">Ops(SelectionKey.OP_ACCEPT)</span>;Copy<br></code></pre></td></tr></table></figure>

<ul>
<li>获得选择器Selector，Netty中使用NioEventloopGroup中的NioEventloop封装了线程和选择器</li>
<li>创建<code>NioServerSocketChannel</code>，该Channel<strong>作为附件</strong>添加到<code>ServerSocketChannel</code>中</li>
<li>创建<code>ServerSocketChannel</code>，将其设置为非阻塞模式，并注册到Selector中，<strong>此时未关注事件，但是添加了附件</strong><code>NioServerSocketChannel</code></li>
<li>绑定端口</li>
<li>通过<code>interestOps</code>设置感兴趣的事件</li>
</ul>
<h3 id="bind"><a href="#bind" class="headerlink" title="bind"></a>bind</h3><p>选择器Selector的创建是在NioEventloopGroup中完成的。<strong>NioServerSocketChannel与ServerSocketChannel的创建，ServerSocketChannel注册到Selector中以及绑定操作都是由<code>bind</code>方法完成的</strong></p>
<p>所以服务器启动的<strong>入口</strong>便是<code>io.netty.bootstrap.ServerBootstrap.bind</code></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public ChannelFuture bind(SocketAddress localAddress) &#123;<br>	validate<span class="hljs-literal">()</span>;<br>	return <span class="hljs-keyword">do</span><span class="hljs-constructor">Bind(ObjectUtil.<span class="hljs-params">checkNotNull</span>(<span class="hljs-params">localAddress</span>, <span class="hljs-string">&quot;localAddress&quot;</span>)</span>);<br>&#125; <br></code></pre></td></tr></table></figure>

<h3 id="doBind"><a href="#doBind" class="headerlink" title="doBind"></a>doBind</h3><p>真正完成初始化、注册以及绑定的<strong>方法是<code>io.netty.bootstrap.AbstractBootstrap.doBind</code></strong></p>
<p><strong>dobind方法在主线程中执行</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-keyword">private</span> ChannelFuture <span class="hljs-keyword">do</span><span class="hljs-constructor">Bind(<span class="hljs-params">final</span> SocketAddress <span class="hljs-params">localAddress</span>)</span> &#123;<br>    <span class="hljs-comment">// 负责NioServerSocketChannel和ServerSocketChannel的创建</span><br>    <span class="hljs-comment">// ServerSocketChannel的注册工作</span><br>    <span class="hljs-comment">// init由main线程完成，regisetr由NIO线程完成</span><br>    final ChannelFuture regFuture = init<span class="hljs-constructor">AndRegister()</span>;<br>    final Channel channel = regFuture.channel<span class="hljs-literal">()</span>;<br>    <span class="hljs-keyword">if</span> (regFuture.cause<span class="hljs-literal">()</span> != null) &#123;<br>        return regFuture;<br>    &#125;<br><br>    <span class="hljs-comment">// 因为register操作是异步的</span><br>    <span class="hljs-comment">// 所以要判断主线程执行到这里时，register操作是否已经执行完毕</span><br>    <span class="hljs-keyword">if</span> (regFuture.is<span class="hljs-constructor">Done()</span>) &#123;<br>        <span class="hljs-comment">// At this point we know that the registration was complete and successful.</span><br>        ChannelPromise promise = channel.<span class="hljs-keyword">new</span><span class="hljs-constructor">Promise()</span>;<br>        <br>        <span class="hljs-comment">// 执行doBind0绑定操作</span><br>        <span class="hljs-keyword">do</span><span class="hljs-constructor">Bind0(<span class="hljs-params">regFuture</span>, <span class="hljs-params">channel</span>, <span class="hljs-params">localAddress</span>, <span class="hljs-params">promise</span>)</span>;<br>        return promise;<br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>        <span class="hljs-comment">// Registration future is almost always fulfilled already, but just in case it&#x27;s not.</span><br>        <span class="hljs-comment">// 如果register操作还没执行完，就会到这个分支中来</span><br>        final PendingRegistrationPromise promise = <span class="hljs-keyword">new</span> <span class="hljs-constructor">PendingRegistrationPromise(<span class="hljs-params">channel</span>)</span>;<br>        <br>        <span class="hljs-comment">// 添加监听器，NIO线程异步进行doBind0操作</span><br>        regFuture.add<span class="hljs-constructor">Listener(<span class="hljs-params">new</span> ChannelFutureListener()</span> &#123;<br>            @Override<br>            public void operation<span class="hljs-constructor">Complete(ChannelFuture <span class="hljs-params">future</span>)</span> throws Exception &#123;<br>                Throwable cause = future.cause<span class="hljs-literal">()</span>;<br>                <span class="hljs-keyword">if</span> (cause != null) &#123;<br>                    <span class="hljs-comment">// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an</span><br>                    <span class="hljs-comment">// IllegalStateException once we try to access the EventLoop of the Channel.</span><br>                    promise.set<span class="hljs-constructor">Failure(<span class="hljs-params">cause</span>)</span>;<br>                &#125; <span class="hljs-keyword">else</span> &#123;<br>                    <span class="hljs-comment">// Registration was successful, so set the correct executor to use.</span><br>                    <span class="hljs-comment">// See https://github.com/netty/netty/issues/2586</span><br>                    promise.registered<span class="hljs-literal">()</span>;<br><br>                    <span class="hljs-keyword">do</span><span class="hljs-constructor">Bind0(<span class="hljs-params">regFuture</span>, <span class="hljs-params">channel</span>, <span class="hljs-params">localAddress</span>, <span class="hljs-params">promise</span>)</span>;<br>                &#125;<br>            &#125;<br>        &#125;);<br>        return promise;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<ul>
<li>doBind()中有两个重要方法<code>initAndRegister()</code>和<code>doBind0(regFuture, channel, localAddress, promise)</code></li>
<li><strong>initAndRegister</strong>主要负责NioServerSocketChannel和ServerSocketChannel的创建（主线程中完成）与ServerSocketChannel注册（NIO线程中完成）工作</li>
<li><strong>doBind0</strong>则负责连接的创建工作</li>
</ul>
<h3 id="initAndRegisterd"><a href="#initAndRegisterd" class="headerlink" title="initAndRegisterd"></a>initAndRegisterd</h3><p>代码</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">final ChannelFuture init<span class="hljs-constructor">AndRegister()</span> &#123;<br>    Channel channel = null;<br>    <span class="hljs-keyword">try</span> &#123;<br>        channel = channelFactory.<span class="hljs-keyword">new</span><span class="hljs-constructor">Channel()</span>;<br>        init(channel);<br>    &#125; catch (Throwable t) &#123;<br>        <span class="hljs-keyword">if</span> (channel != null) &#123;<br>            <span class="hljs-comment">// channel can be null if newChannel crashed (eg SocketException(&quot;too many open files&quot;))</span><br>            channel.unsafe<span class="hljs-literal">()</span>.close<span class="hljs-constructor">Forcibly()</span>;<br>            <span class="hljs-comment">// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor</span><br>            return <span class="hljs-keyword">new</span> <span class="hljs-constructor">DefaultChannelPromise(<span class="hljs-params">channel</span>, GlobalEventExecutor.INSTANCE)</span>.set<span class="hljs-constructor">Failure(<span class="hljs-params">t</span>)</span>;<br>        &#125;<br>        <span class="hljs-comment">// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor</span><br>        return <span class="hljs-keyword">new</span> <span class="hljs-constructor">DefaultChannelPromise(<span class="hljs-params">new</span> FailedChannel()</span>, GlobalEventExecutor.INSTANCE).set<span class="hljs-constructor">Failure(<span class="hljs-params">t</span>)</span>;<br>    &#125;<br><br>    ChannelFuture regFuture = config<span class="hljs-literal">()</span>.group<span class="hljs-literal">()</span>.register(channel);<br>    <span class="hljs-keyword">if</span> (regFuture.cause<span class="hljs-literal">()</span> != null) &#123;<br>        <span class="hljs-keyword">if</span> (channel.is<span class="hljs-constructor">Registered()</span>) &#123;<br>            channel.close<span class="hljs-literal">()</span>;<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            channel.unsafe<span class="hljs-literal">()</span>.close<span class="hljs-constructor">Forcibly()</span>;<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">// If we are here and the promise is not failed, it&#x27;s one of the following cases:</span><br>    <span class="hljs-comment">// 1) If we attempted registration from the event loop, the registration has been completed at this point.</span><br>    <span class="hljs-comment">//    i.e. It&#x27;s safe to attempt bind() or connect() now because the channel has been registered.</span><br>    <span class="hljs-comment">// 2) If we attempted registration from the other thread, the registration request has been successfully</span><br>    <span class="hljs-comment">//    added to the event loop&#x27;s task queue for later execution.</span><br>    <span class="hljs-comment">//    i.e. It&#x27;s safe to attempt bind() or connect() now:</span><br>    <span class="hljs-comment">//         because bind() or connect() will be executed *after* the scheduled registration task is executed</span><br>    <span class="hljs-comment">//         because register(), bind(), and connect() are all bound to the same thread.</span><br><br>    return regFuture;<br>&#125; <br></code></pre></td></tr></table></figure>

<h4 id="init"><a href="#init" class="headerlink" title="init"></a>init</h4><figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs haxe">Channel channel = <span class="hljs-literal">null</span>;<br><span class="hljs-keyword">try</span> &#123;<br>    <span class="hljs-comment">// 通过反射初始化NioServerSocketChannel</span><br>    channel = channelFactory.<span class="hljs-keyword">new</span><span class="hljs-type">Channel</span>();<br>    init(channel);<br>&#125; <br></code></pre></td></tr></table></figure>

<p>newChannel方法</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> T <span class="hljs-function"><span class="hljs-title">newChannel</span>(<span class="hljs-params"></span>)</span> &#123;<br>    <span class="hljs-keyword">try</span> &#123;<br>        <span class="hljs-comment">// 通过反射调用NioServerSocketChannel的构造方法</span><br>        <span class="hljs-comment">// 创建NioServerSocketChannel对象</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-title">constructor</span>.<span class="hljs-title">newInstance</span>(<span class="hljs-params"></span>);<br>    &#125; <span class="hljs-keyword">catch</span> (Throwable t) &#123;<br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ChannelException(<span class="hljs-string">&quot;Unable to create Channel from class &quot;</span> + <span class="hljs-title">constructor</span>.<span class="hljs-title">getDeclaringClass</span>(<span class="hljs-params"></span>), <span class="hljs-title">t</span>);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>NioServerSocketChannel构造方法</p>
<figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs haxe"><span class="hljs-keyword">public</span> NioServerSocketChannel() &#123;<br>    <span class="hljs-comment">// 创建了ServerSocketChannel实例</span><br>    <span class="hljs-built_in">this</span>(<span class="hljs-keyword">new</span><span class="hljs-type">Socket</span>(DEFAULT_SELECTOR_PROVIDER));<br>&#125; <br></code></pre></td></tr></table></figure>

<p>newSocket方法</p>
<figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs haxe"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> ServerSocketChannel <span class="hljs-keyword">new</span><span class="hljs-type">Socket</span>(SelectorProvider provider) &#123;<br>    <span class="hljs-keyword">try</span> &#123;<br>        <span class="hljs-comment">// ServerSocketChannel.open方法：</span><br>        <span class="hljs-comment">// SelectorProvider.provider().openServerSocketChannel()</span><br>	    <span class="hljs-comment">// 所以此处相当于ServerSocketChannel.open()</span><br>        <span class="hljs-comment">// 创建了ServerSocketChannel实例</span><br>    	<span class="hljs-keyword">return</span> provider.openServerSocketChannel();<br>	&#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>  	  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-type">ChannelException</span>(<span class="hljs-string">&quot;Failed to open a server socket.&quot;</span>, e);<br>	&#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>init方法</strong></p>
<figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs aspectj"><span class="hljs-meta">@Override</span><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">(Channel channel)</span> </span>&#123;<br>   	...<br>		<br>    <span class="hljs-comment">// NioSocketChannl的Pipeline    </span><br>    ChannelPipeline p = channel.pipeline();<br>		<br>    ...<br><br>    <span class="hljs-comment">// 向Pipeline中添加了一个handler，该handler等待被调用</span><br>    p.addLast(<span class="hljs-keyword">new</span> ChannelInitializer&lt;Channel&gt;() &#123;<br>        <span class="hljs-meta">@Override</span><br>        <span class="hljs-comment">// register之后才调用该方法</span><br>        <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(<span class="hljs-keyword">final</span> Channel ch)</span> </span>&#123;<br>            <span class="hljs-keyword">final</span> ChannelPipeline pipeline = ch.pipeline();<br>            <br>            <span class="hljs-comment">// 创建handler并加入到pipeline中</span><br>            ChannelHandler <span class="hljs-keyword">handler</span> = config.<span class="hljs-keyword">handler</span>();<br>            <span class="hljs-keyword">if</span> (<span class="hljs-keyword">handler</span> != <span class="hljs-keyword">null</span>) &#123;<br>                pipeline.addLast(<span class="hljs-keyword">handler</span>);<br>            &#125;<br><br>            ch.eventLoop().execute(<span class="hljs-keyword">new</span> Runnable() &#123;<br>                <span class="hljs-meta">@Override</span><br>                <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;<br>                    <span class="hljs-comment">// 添加新的handler，在发生Accept事件后建立连接</span><br>                    pipeline.addLast(<span class="hljs-keyword">new</span> ServerBootstrapAcceptor(<br>                            ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));<br>                &#125;<br>            &#125;);<br>        &#125;<br>    &#125;);<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>init主要完成了以下三个操作</strong></p>
<ul>
<li><p>创建NioServerSocketChannel</p>
</li>
<li><p>通过NioServerSocketChannel的构造器，创建了ServerSocketChannel</p>
</li>
<li><p>由</p>
<figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ebnf"><span class="hljs-attribute">initChannel</span><br></code></pre></td></tr></table></figure>

<p>方法向NioServerSocketChannel中添加了两个handler，</p>
<p>添加操作在register之后被执行</p>
<ul>
<li>一个handler负责设置配置</li>
<li>一个handler负责发生Accepet事件后建立连接</li>
</ul>
</li>
</ul>
<h4 id="Register"><a href="#Register" class="headerlink" title="Register"></a>Register</h4><p>init执行完毕后，便执行<code>ChannelFuture regFuture = config().group().register(channel)</code>操作</p>
<p>该方法最终调用的是<code>promise.channel().unsafe().register(this, promise)</code>方法</p>
<p><strong>promise.channel().unsafe().register(this, promise)</strong></p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> final <span class="hljs-built_in">void</span> <span class="hljs-function"><span class="hljs-title">register</span>(<span class="hljs-params">EventLoop eventLoop, final ChannelPromise promise</span>)</span> &#123;<br>    ...<br><br>    <span class="hljs-comment">// 获取EventLoop</span><br>    AbstractChannel.this.eventLoop = eventLoop;<br><br>   	<span class="hljs-comment">// 此处完成了由 主线程 到 NIO线程 的切换</span><br>    <span class="hljs-comment">// eventLoop.inEventLoop()用于判断当前线程是否为NIO线程</span><br>    <span class="hljs-keyword">if</span> (eventLoop.inEventLoop()) &#123;<br>        register0(promise);<br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">// 向NIO线程中添加任务</span><br>            eventLoop.execute(<span class="hljs-keyword">new</span> <span class="hljs-function"><span class="hljs-title">Runnable</span>(<span class="hljs-params"></span>)</span> &#123;<br>                <span class="hljs-meta">@Override</span><br>                <span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> <span class="hljs-function"><span class="hljs-title">run</span>(<span class="hljs-params"></span>)</span> &#123;<br>                    <span class="hljs-comment">// 该方法中会执行doRegister</span><br>                    <span class="hljs-comment">// 执行真正的注册操作</span><br>                    register0(promise);<br>                &#125;<br>            &#125;);<br>        &#125; <span class="hljs-keyword">catch</span> (Throwable t) &#123;<br>           ...<br>        &#125;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>register0方法</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">private</span> <span class="hljs-built_in">void</span> <span class="hljs-function"><span class="hljs-title">register0</span>(<span class="hljs-params">ChannelPromise promise</span>)</span> &#123;<br>    <span class="hljs-keyword">try</span> &#123;<br>       	...<br>            <br>        <span class="hljs-comment">// 执行真正的注册操作</span><br>        doRegister();<br>        neverRegistered = <span class="hljs-literal">false</span>;<br>        registered = <span class="hljs-literal">true</span>;<br><br>        <span class="hljs-comment">// Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the</span><br>        <span class="hljs-comment">// user may already fire events through the pipeline in the ChannelFutureListener.</span><br>        <br>        <span class="hljs-comment">// 调用init中的initChannel方法</span><br>        pipeline.invokeHandlerAddedIfNeeded();<br><br>        ...<br>    &#125; <span class="hljs-keyword">catch</span> (Throwable t) &#123;<br>        ...<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>doRegister方法</strong></p>
<figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs aspectj"><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">doRegister</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;<br>    <span class="hljs-keyword">boolean</span> selected = <span class="hljs-keyword">false</span>;<br>    <span class="hljs-keyword">for</span> (;;) &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">// javaChannel()即为ServerSocketChannel</span><br>            <span class="hljs-comment">// eventLoop().unwrappedSelector()获取eventLoop中的Selector</span><br>            <span class="hljs-comment">// this为NIOServerSocketChannel，作为附件</span><br>            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), <span class="hljs-number">0</span>, <span class="hljs-keyword">this</span>);<br>            <span class="hljs-keyword">return</span>;<br>        &#125; <span class="hljs-keyword">catch</span> (CancelledKeyException e) &#123;<br>            ...<br>           <br>        &#125;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>回调initChannel</p>
<figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs aspectj"><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(<span class="hljs-keyword">final</span> Channel ch)</span> </span>&#123;<br>    <span class="hljs-keyword">final</span> ChannelPipeline pipeline = ch.pipeline();<br>    ChannelHandler <span class="hljs-keyword">handler</span> = config.<span class="hljs-keyword">handler</span>();<br>    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">handler</span> != <span class="hljs-keyword">null</span>) &#123;<br>        pipeline.addLast(<span class="hljs-keyword">handler</span>);<br>    &#125;<br><br>    <span class="hljs-comment">// 添加新任务，任务负责添加handler</span><br>    <span class="hljs-comment">// 该handler负责发生Accepet事件后建立连接</span><br>    ch.eventLoop().execute(<span class="hljs-keyword">new</span> Runnable() &#123;<br>        <span class="hljs-meta">@Override</span><br>        <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;<br>            pipeline.addLast(<span class="hljs-keyword">new</span> ServerBootstrapAcceptor(<br>                    ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));<br>        &#125;<br>    &#125;);<br>&#125; <br></code></pre></td></tr></table></figure>

<p>Register主要完成了以下三个操作</p>
<ul>
<li><p>完成了主线程到NIO的<strong>线程切换</strong></p>
<ul>
<li>通过<code>eventLoop.inEventLoop()</code>进行线程判断，判断当前线程是否为NIO线程</li>
<li>切换的方式为让eventLoop执行register的操作</li>
<li><strong>register的操作在NIO线程中完成</strong></li>
</ul>
</li>
<li><p><strong>调用doRegister方法</strong></p>
<figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs awk"><span class="hljs-regexp">//</span> javaChannel()即为ServerSocketChannel<br><span class="hljs-regexp">//</span> eventLoop().unwrappedSelector()获取eventLoop中的Selector<br><span class="hljs-regexp">//</span> this为NIOServerSocketChannel，作为附件<br>selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), <span class="hljs-number">0</span>, this);Copy<br></code></pre></td></tr></table></figure>

<ul>
<li><strong>将ServerSocketChannel注册到EventLoop的Selector中</strong></li>
<li>此时还未关注事件</li>
<li>添加NioServerSocketChannel附件</li>
</ul>
</li>
<li><p>通过<code>invokeHandlerAddedIfNeeded</code>调用init中的<code>initChannel</code>方法</p>
<ul>
<li><p>initChannel方法主要创建了</p>
<p>两个handler</p>
<ul>
<li>一个handler负责设置配置</li>
<li>一个handler负责发生Accept事件后建立连接</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="doBind0"><a href="#doBind0" class="headerlink" title="doBind0"></a>doBind0</h3><h4 id="绑定端口"><a href="#绑定端口" class="headerlink" title="绑定端口"></a>绑定端口</h4><p>在<code>doRegister</code>和<code>invokeHandlerAddedIfNeeded</code>操作中的完成后，会调用<code>safeSetSuccess(promise)</code>方法，向Promise中设置执行成功的结果。此时<code>doBind</code>方法中由<code>initAndRegister</code>返回的ChannelFuture对象regFuture便会由NIO线程异步执行doBind0绑定操作</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-comment">// initAndRegister为异步方法，会返回ChannelFuture对象</span><br>final ChannelFuture regFuture = init<span class="hljs-constructor">AndRegister()</span>;<br>regFuture.add<span class="hljs-constructor">Listener(<span class="hljs-params">new</span> ChannelFutureListener()</span> &#123;<br>    @Override<br>    public void operation<span class="hljs-constructor">Complete(ChannelFuture <span class="hljs-params">future</span>)</span> throws Exception &#123;<br>        Throwable cause = future.cause<span class="hljs-literal">()</span>;<br>        <span class="hljs-keyword">if</span> (cause != null) &#123;<br>            <span class="hljs-comment">// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an</span><br>            <span class="hljs-comment">// IllegalStateException once we try to access the EventLoop of the Channel.</span><br>            promise.set<span class="hljs-constructor">Failure(<span class="hljs-params">cause</span>)</span>;<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            <span class="hljs-comment">// Registration was successful, so set the correct executor to use.</span><br>            <span class="hljs-comment">// See https://github.com/netty/netty/issues/2586</span><br>            promise.registered<span class="hljs-literal">()</span>;<br>            <span class="hljs-comment">// 如果没有异常，则执行绑定操作</span><br>            <span class="hljs-keyword">do</span><span class="hljs-constructor">Bind0(<span class="hljs-params">regFuture</span>, <span class="hljs-params">channel</span>, <span class="hljs-params">localAddress</span>, <span class="hljs-params">promise</span>)</span>;<br>        &#125;<br>    &#125;<br>&#125;);Copy<br></code></pre></td></tr></table></figure>

<p><strong>doBind0最底层调用的是ServerSocketChannel的bind方法</strong></p>
<p>NioServerSocketChannel.doBind方法</p>
<p>通过该方法，绑定了对应的端口</p>
<figure class="highlight less"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs less"><span class="hljs-variable">@SuppressJava6Requirement</span>(reason = <span class="hljs-string">&quot;Usage guarded by java version check&quot;</span>)<br><span class="hljs-variable">@Override</span><br>protected void doBind(SocketAddress localAddress) throws Exception &#123;<br>    <span class="hljs-selector-tag">if</span> (PlatformDependent.javaVersion() &gt;= <span class="hljs-number">7</span>) &#123;<br>        <span class="hljs-comment">// 调用ServerSocketChannel的bind方法，绑定端口</span><br>        <span class="hljs-selector-tag">javaChannel</span>()<span class="hljs-selector-class">.bind</span>(localAddress, config.getBacklog());<br>    &#125; <span class="hljs-selector-tag">else</span> &#123;<br>        <span class="hljs-selector-tag">javaChannel</span>()<span class="hljs-selector-class">.socket</span>()<span class="hljs-selector-class">.bind</span>(localAddress, config.getBacklog());<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h4 id="关注事件"><a href="#关注事件" class="headerlink" title="关注事件"></a>关注事件</h4><p>在绑定端口操作完成后，会判断各种所有初始化操作是否已经完成，若完成，则会添加ServerSocketChannel感兴趣的事件</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">if</span> (!wasActive &amp;&amp; isActive()) &#123;<br>    invokeLater(<span class="hljs-keyword">new</span> <span class="hljs-function"><span class="hljs-title">Runnable</span>(<span class="hljs-params"></span>)</span> &#123;<br>        <span class="hljs-meta">@Override</span><br>        <span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> <span class="hljs-function"><span class="hljs-title">run</span>(<span class="hljs-params"></span>)</span> &#123;<br>            pipeline.fireChannelActive();<br>        &#125;<br>    &#125;);<br>&#125; <br></code></pre></td></tr></table></figure>

<p>最终在<code>AbstractNioChannel.doBeginRead</code>方法中，会添加ServerSocketChannel添加Accept事件</p>
<figure class="highlight angelscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs angelscript">@Override<br><span class="hljs-keyword">protected</span> <span class="hljs-built_in">void</span> doBeginRead() throws Exception &#123;<br>    <span class="hljs-comment">// Channel.read() or ChannelHandlerContext.read() was called</span><br>    <span class="hljs-keyword">final</span> SelectionKey selectionKey = <span class="hljs-keyword">this</span>.selectionKey;<br>    <span class="hljs-keyword">if</span> (!selectionKey.isValid()) &#123;<br>        <span class="hljs-keyword">return</span>;<br>    &#125;<br>    readPending = <span class="hljs-literal">true</span>;<br>    <span class="hljs-keyword">final</span> <span class="hljs-built_in">int</span> <span class="hljs-built_in">int</span>erestOps = selectionKey.<span class="hljs-built_in">int</span>erestOps();<br>    <span class="hljs-comment">// 如果ServerSocketChannel没有关注Accept事件</span><br>    <span class="hljs-keyword">if</span> ((<span class="hljs-built_in">int</span>erestOps &amp; readInterestOp) == <span class="hljs-number">0</span>) &#123;<br>        <span class="hljs-comment">// 则让其关注Accepet事件</span><br>        <span class="hljs-comment">// readInterestOp 取值是 16</span><br>        <span class="hljs-comment">// 在 NioServerSocketChannel 创建时初始化</span><br>        selectionKey.<span class="hljs-built_in">int</span>erestOps(<span class="hljs-built_in">int</span>erestOps | readInterestOp);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>注意</strong>：此处设置interestOps时使用的方法，<strong>避免覆盖关注的其他事件</strong></p>
<ul>
<li><p>首先获取Channel所有感兴趣的事件</p>
<figure class="highlight angelscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs angelscript"><span class="hljs-keyword">final</span> <span class="hljs-built_in">int</span> <span class="hljs-built_in">int</span>erestOps = selectionKey.<span class="hljs-built_in">int</span>erestOps();Copy<br></code></pre></td></tr></table></figure></li>
<li><p>然后再设置其感兴趣的事件</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">selectionKey.interest<span class="hljs-constructor">Ops(<span class="hljs-params">interestOps</span> | <span class="hljs-params">readInterestOp</span>)</span>;Copy<br></code></pre></td></tr></table></figure></li>
</ul>
<p><strong>各个事件对应的值</strong></p>
<p><img src="https://gitee.com/accept_one_time/pic-bed/raw/master/img/20220130120330.png" srcset="/xt-blog/img/loading.gif" lazyload alt="img"></p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>通过上述步骤，完成了</p>
<ul>
<li>NioServerSocketChannel与ServerSocketChannel的创建</li>
<li>ServerSocketChannel绑定到EventLoop的Selecot中，并添加NioServerSocketChannel附件</li>
<li>绑定了对应的端口</li>
<li>关注了Accept事件</li>
</ul>
<h2 id="2、NioEventLoop剖析"><a href="#2、NioEventLoop剖析" class="headerlink" title="2、NioEventLoop剖析"></a>2、NioEventLoop剖析</h2><h3 id="组成-1"><a href="#组成-1" class="headerlink" title="组成"></a>组成</h3><p>NioEventLoop的重要组成部分有三个</p>
<ul>
<li><p><strong>Selector</strong></p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs scala">public <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NioEventLoop</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SingleThreadEventLoop</span> </span>&#123;<br>    <br>    ...<br>        <br>    <span class="hljs-comment">// selector中的selectedKeys是基于数组的</span><br>    <span class="hljs-comment">// unwrappedSelector中的selectedKeys是基于HashSet的    </span><br>    <span class="hljs-keyword">private</span> <span class="hljs-type">Selector</span> selector;<br>    <span class="hljs-keyword">private</span> <span class="hljs-type">Selector</span> unwrappedSelector;<br>    <span class="hljs-keyword">private</span> <span class="hljs-type">SelectedSelectionKeySet</span> selectedKeys;<br>    <br>    ...<br>&#125; <br></code></pre></td></tr></table></figure></li>
<li><p><strong>Thread与TaskQueue</strong></p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs scala">public <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SingleThreadEventExecutor</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractScheduledEventExecutor</span> <span class="hljs-title">implements</span> <span class="hljs-title">OrderedEventExecutor</span> </span>&#123;<br>    <span class="hljs-comment">// 任务队列</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Queue</span>&lt;<span class="hljs-type">Runnable</span>&gt; taskQueue;<br><br>    <span class="hljs-comment">// 线程</span><br>    <span class="hljs-keyword">private</span> volatile <span class="hljs-type">Thread</span> thread;<br>&#125; <br></code></pre></td></tr></table></figure></li>
</ul>
<h4 id="Selector的创建"><a href="#Selector的创建" class="headerlink" title="Selector的创建"></a>Selector的创建</h4><p>Selector是在NioEventLoop的构造方法中被创建的</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-constructor">NioEventLoop(NioEventLoopGroup <span class="hljs-params">parent</span>, Executor <span class="hljs-params">executor</span>, SelectorProvider <span class="hljs-params">selectorProvider</span>, SelectStrategy <span class="hljs-params">strategy</span>, RejectedExecutionHandler <span class="hljs-params">rejectedExecutionHandler</span>, EventLoopTaskQueueFactory <span class="hljs-params">queueFactory</span>)</span> &#123;<span class="hljs-operator"></span><br><span class="hljs-operator">    </span><br><span class="hljs-operator">        ...</span><br><span class="hljs-operator">           </span><br><span class="hljs-operator">        </span><span class="hljs-comment">// 初始化selector，初始化过程在openSelector中</span><br>        final SelectorTuple selectorTuple = <span class="hljs-keyword">open</span><span class="hljs-constructor">Selector()</span>;<br>        this.selector = selectorTuple.selector;<br>        this.unwrappedSelector = selectorTuple.unwrappedSelector;<br>&#125;<br><br><br><span class="hljs-keyword">private</span> SelectorTuple <span class="hljs-keyword">open</span><span class="hljs-constructor">Selector()</span> &#123;<br>    final Selector unwrappedSelector;<br>    <span class="hljs-keyword">try</span> &#123;<br>        <span class="hljs-comment">// 此处等同于 Selector.open()方法</span><br>        <span class="hljs-comment">// 创建了unwrappedSelector对象</span><br>        unwrappedSelector = provider.<span class="hljs-keyword">open</span><span class="hljs-constructor">Selector()</span>;<br>    &#125; catch (IOException e) &#123;<br>        throw <span class="hljs-keyword">new</span> <span class="hljs-constructor">ChannelException(<span class="hljs-string">&quot;failed to open a new selector&quot;</span>, <span class="hljs-params">e</span>)</span>;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>NioEventLoop的构造方法中，调用了<code>openSelector()</code>方法， 该方法会返回一个</p>
<p><strong>SelectorTuple</strong>对象，该方法<strong>是创建Selector的核心方法</strong>。<code>openSelector()</code>方法内部调用了</p>
<figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ini"><span class="hljs-attr">unwrappedSelector</span> = provider.openSelector()<span class="hljs-comment">;Copy</span><br></code></pre></td></tr></table></figure>

<p>获得了Selector对象<code>unwrappedSelector</code></p>
<p>后面会通过反射，修改<code>unwrappedSelector</code>中SelectedKeys的实现，然后通过<code>SelectedSelectionKeySetSelector</code>方法获得selector。<strong>最后通过SelectorTuple的构造方法，将该Selector的值赋给SelectorTuple类中的selector与unwrappedSelector</strong></p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-keyword">private</span> static <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SelectorTuple</span> </span>&#123;<br>    <span class="hljs-keyword">final</span> Selector unwrappedSelector;<br>    <span class="hljs-keyword">final</span> Selector selector;<br><br>    SelectorTuple(Selector unwrappedSelector) &#123;<br>        <span class="hljs-keyword">this</span>.unwrappedSelector = unwrappedSelector;<br>        <span class="hljs-keyword">this</span>.selector = unwrappedSelector;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">    * 一般调用的是这个构造方法</span><br><span class="hljs-comment">    */</span><br>    SelectorTuple(Selector unwrappedSelector, Selector selector) &#123;<br>        <span class="hljs-keyword">this</span>.unwrappedSelector = unwrappedSelector;<br>        <span class="hljs-keyword">this</span>.selector = selector;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>再通过NioEventLoop的构造方法，将SelectorTuple中的Selector赋值给NioEventLoop中的Selector</strong></p>
<h4 id="两个Selector"><a href="#两个Selector" class="headerlink" title="两个Selector"></a>两个Selector</h4><p>NioEventLoop中有selector和unwrappedSelector两个Selector，<strong>它们的区别主要在于SelectedKeys的数据结构</strong></p>
<ul>
<li>selector中的SelectedKeys是<strong>基于数组</strong>的</li>
<li>unwrappedSelector中的是<strong>基于HashSet</strong>的</li>
</ul>
<p>这样做的主要目的是，<strong>数组的遍历效率要高于HashSet</strong></p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">private</span> SelectorTuple <span class="hljs-function"><span class="hljs-title">openSelector</span>(<span class="hljs-params"></span>)</span> &#123;<br>    final Selector unwrappedSelector;<br>    <span class="hljs-keyword">try</span> &#123;<br>        unwrappedSelector = provider.openSelector();<br>    &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ChannelException(<span class="hljs-string">&quot;failed to open a new selector&quot;</span>, e);<br>    &#125;<br><br>    ...<br>    <br>    <span class="hljs-comment">// 获得基于数组的selectedKeySet实现</span><br>    final SelectedSelectionKeySet selectedKeySet = <span class="hljs-keyword">new</span> SelectedSelectionKeySet();<br><br><br>    <span class="hljs-built_in">Object</span> maybeException = AccessController.doPrivileged(<span class="hljs-keyword">new</span> PrivilegedAction&lt;<span class="hljs-built_in">Object</span>&gt;() &#123;<br>        <span class="hljs-meta">@Override</span><br>        <span class="hljs-keyword">public</span> <span class="hljs-built_in">Object</span> <span class="hljs-function"><span class="hljs-title">run</span>(<span class="hljs-params"></span>)</span> &#123;<br>            <span class="hljs-keyword">try</span> &#123;<br>                <span class="hljs-comment">// 通过反射拿到unwrappedSelector中的selectedKeys属性</span><br>                Field selectedKeysField = selectorImplClass.getDeclaredField(<span class="hljs-string">&quot;selectedKeys&quot;</span>);<br>                Field publicSelectedKeysField = selectorImplClass.getDeclaredField(<span class="hljs-string">&quot;publicSelectedKeys&quot;</span>);<br><br>                ...<br>	<br>                <span class="hljs-comment">// 暴力反射，修改私有属性</span><br>                Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, <span class="hljs-literal">true</span>);<br>                <span class="hljs-keyword">if</span> (cause != <span class="hljs-literal">null</span>) &#123;<br>                    <span class="hljs-keyword">return</span> cause;<br>                &#125;<br>                cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, <span class="hljs-literal">true</span>);<br>                <span class="hljs-keyword">if</span> (cause != <span class="hljs-literal">null</span>) &#123;<br>                    <span class="hljs-keyword">return</span> cause;<br>                &#125;<br><br>                <span class="hljs-comment">// 替换为基于数组的selectedKeys实现</span><br>                selectedKeysField.set(unwrappedSelector, selectedKeySet);<br>                publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);<br>                <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>            &#125; <span class="hljs-keyword">catch</span> (NoSuchFieldException e) &#123;<br>                <span class="hljs-keyword">return</span> e;<br>            &#125; <span class="hljs-keyword">catch</span> (IllegalAccessException e) &#123;<br>                <span class="hljs-keyword">return</span> e;<br>            &#125;<br>        &#125;<br>    &#125;);<br><br>    selectedKeys = selectedKeySet;<br>    <br>    <span class="hljs-comment">// 调用构造函数，创建unwrappedSelector与selector</span><br>    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> SelectorTuple(unwrappedSelector,<br>                             <span class="hljs-keyword">new</span> SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));<br>&#125; <br></code></pre></td></tr></table></figure>

<p>获得数组实现SelectedKeys的Selector的原理是反射，主要步骤如下</p>
<ul>
<li><p>获得<strong>基于数组</strong>的selectedKeySet实现</p>
<figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs haxe"><span class="hljs-comment">// 获得基于数组的selectedKeySet实现</span><br>final SelectedSelectionKeySet selectedKeySet = <span class="hljs-keyword">new</span> <span class="hljs-type">SelectedSelectionKeySet</span>();<br><br>SelectedSelectionKeySet() &#123;<br>	keys = <span class="hljs-keyword">new</span> <span class="hljs-type">SelectionKey</span>[<span class="hljs-number">1024</span>];<br>&#125; <br></code></pre></td></tr></table></figure></li>
<li><p>通过<strong>反射</strong>拿到unwrappedSelector中的SelectedKeySet并将其<strong>替换为selectedKeySet</strong></p>
</li>
<li><p><strong>通过Selector的构造方法</strong>获得selector</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-keyword">new</span> <span class="hljs-constructor">SelectedSelectionKeySetSelector(<span class="hljs-params">unwrappedSelector</span>, <span class="hljs-params">selectedKeySet</span>)</span> <br></code></pre></td></tr></table></figure></li>
<li><p><strong>通过SelectorTuple的构造方法</strong>获得拥有两种Selector的SelectorTuple对象，并返回给NioEventLoop</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-comment">// 调用构造函数，创建unwrappedSelector与selector</span><br>return <span class="hljs-keyword">new</span> <span class="hljs-constructor">SelectorTuple(<span class="hljs-params">unwrappedSelector</span>, <span class="hljs-params">new</span> SelectedSelectionKeySetSelector(<span class="hljs-params">unwrappedSelector</span>, <span class="hljs-params">selectedKeySet</span>)</span>);Copy<br></code></pre></td></tr></table></figure></li>
</ul>
<h3 id="NIO线程启动时机"><a href="#NIO线程启动时机" class="headerlink" title="NIO线程启动时机"></a>NIO线程启动时机</h3><h4 id="启动"><a href="#启动" class="headerlink" title="启动"></a>启动</h4><p>NioEventLoop中的线程，<strong>在首次执行任务时，才会被创建，且只会被创建一次</strong></p>
<p><strong>测试代码</strong></p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs arduino"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestNioEventLoop</span> &#123;</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">String</span>[] args)</span> </span>&#123;<br>        EventLoop eventLoop = <span class="hljs-keyword">new</span> <span class="hljs-built_in">NioEventLoopGroup</span>().<span class="hljs-built_in">next</span>();<br>        <span class="hljs-comment">// 使用NioEventLoop执行任务</span><br>        eventLoop.<span class="hljs-built_in">execute</span>(()-&gt;&#123;<br>            System.out.<span class="hljs-built_in">println</span>(<span class="hljs-string">&quot;hello&quot;</span>);<br>        &#125;);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>进入<code>execute</code>执行任务</p>
<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs gradle">@Override<br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> execute(Runnable <span class="hljs-keyword">task</span>) &#123;<br>    <span class="hljs-comment">// 检测传入的任务是否为空，为空会抛出NullPointerException</span><br>    ObjectUtil.checkNotNull(<span class="hljs-keyword">task</span>, <span class="hljs-string">&quot;task&quot;</span>);<br>    <span class="hljs-comment">// 执行任务</span><br>    <span class="hljs-comment">// 此处判断了任务是否为懒加载任务，wakesUpForTask的返回值只会为true</span><br>    execute(<span class="hljs-keyword">task</span>, !(<span class="hljs-keyword">task</span> <span class="hljs-keyword">instanceof</span> LazyRunnable) &amp;&amp; wakesUpForTask(<span class="hljs-keyword">task</span>));<br>&#125; <br></code></pre></td></tr></table></figure>

<p>进入上述代码的<code>execute</code>方法</p>
<figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs awk">private void execute(Runnable task, boolean immediate) &#123;<br>    <span class="hljs-regexp">//</span> 判断当前线程是否为NIO线程<br>    <span class="hljs-regexp">//</span> 判断方法为 return thread == this.thread;<br>    <span class="hljs-regexp">//</span> this.thread即为NIO线程，首次执行任务时，其为null<br>    boolean inEventLoop = inEventLoop();<br>    <br>    <span class="hljs-regexp">//</span> 向任务队列taskQueue中添加任务<br>    addTask(task);<br>    <br>    <span class="hljs-regexp">//</span> 当前线程不是NIO线程，则进入<span class="hljs-keyword">if</span>语句<br>    <span class="hljs-keyword">if</span> (!inEventLoop) &#123;<br>        <span class="hljs-regexp">//</span> 启动NIO线程的核心方法<br>        startThread();<br>        <br>        ...<br>        <br>    &#125;<br>	<br>    <span class="hljs-regexp">//</span> 有任务需要被执行时，唤醒阻塞的NIO线程<br>    <span class="hljs-keyword">if</span> (!addTaskWakesUp &amp;&amp; immediate) &#123;<br>        wakeup(inEventLoop);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>进入<code>startThread</code>方法</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-keyword">private</span> void start<span class="hljs-constructor">Thread()</span> &#123;<br>    <span class="hljs-comment">// 查看NIO线程状态是否为未启动</span><br>    <span class="hljs-comment">// 该if代码块只会执行一次</span><br>    <span class="hljs-comment">// state一开始的值就是ST_NOT_STARTED</span><br>    <span class="hljs-comment">// private volatile int state = ST_NOT_STARTED;</span><br>    <span class="hljs-keyword">if</span> (state<span class="hljs-operator"> == </span>ST_NOT_STARTED) &#123;<br>        <span class="hljs-comment">// 通过原子属性更新器将状态更新为启动（ST_STARTED）</span><br>        <span class="hljs-keyword">if</span> (<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">STATE_UPDATER</span>.</span></span>compare<span class="hljs-constructor">AndSet(<span class="hljs-params">this</span>, ST_NOT_STARTED, ST_STARTED)</span>) &#123;<br>            boolean success = <span class="hljs-literal">false</span>;<br>            <span class="hljs-keyword">try</span> &#123;<br>                <span class="hljs-comment">// 执行启动线程</span><br>                <span class="hljs-keyword">do</span><span class="hljs-constructor">StartThread()</span>;<br>                success = <span class="hljs-literal">true</span>;<br>            &#125; finally &#123;<br>                <span class="hljs-keyword">if</span> (!success) &#123;<br>                    <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">STATE_UPDATER</span>.</span></span>compare<span class="hljs-constructor">AndSet(<span class="hljs-params">this</span>, ST_STARTED, ST_NOT_STARTED)</span>;<br>                &#125;<br>            &#125;<br>        &#125;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>进入<code>doStartThread</code>，真正创建NIO线程并执行任务</strong></p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">private</span> <span class="hljs-built_in">void</span> <span class="hljs-function"><span class="hljs-title">doStartThread</span>(<span class="hljs-params"></span>)</span> &#123;<br>    assert thread == <span class="hljs-literal">null</span>;<br>    <span class="hljs-comment">// 创建NIO线程并执行任务</span><br>    executor.execute(<span class="hljs-keyword">new</span> <span class="hljs-function"><span class="hljs-title">Runnable</span>(<span class="hljs-params"></span>)</span> &#123;<br>        <span class="hljs-meta">@Override</span><br>        <span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> <span class="hljs-function"><span class="hljs-title">run</span>(<span class="hljs-params"></span>)</span> &#123;<br>            <span class="hljs-comment">// thread即为NIO线程</span><br>            thread = Thread.currentThread();<br>            <span class="hljs-keyword">if</span> (interrupted) &#123;<br>                thread.interrupt();<br>            &#125;<br><br>            <span class="hljs-built_in">boolean</span> success = <span class="hljs-literal">false</span>;<br>            updateLastExecutionTime();<br>            <span class="hljs-keyword">try</span> &#123;<br>                <span class="hljs-comment">// 执行内部run方法</span><br>                SingleThreadEventExecutor.this.run();<br>                success = <span class="hljs-literal">true</span>;<br>            &#125; <br>            <br>            ...<br>    &#125;);<br>&#125; <br></code></pre></td></tr></table></figure>

<p>通过<code>SingleThreadEventExecutor.this.run()</code>执行传入的任务（task）</p>
<p>该run方法是<strong>NioEvnetLoop的run方法</strong></p>
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><code class="hljs csharp">@Override<br><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>(<span class="hljs-params"></span>)</span> &#123;<br>    <span class="hljs-built_in">int</span> selectCnt = <span class="hljs-number">0</span>;<br>    <span class="hljs-comment">// 死循环，不断地从任务队列中获取各种任务来执行</span><br>    <span class="hljs-keyword">for</span> (;;) &#123;	<br>      	<span class="hljs-comment">// 执行各种任务</span><br>   		<span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-built_in">int</span> strategy;<br>            <span class="hljs-keyword">try</span> &#123;<br>                strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());<br>                <span class="hljs-keyword">switch</span> (strategy) &#123;<br>                <span class="hljs-keyword">case</span> SelectStrategy.CONTINUE:<br>                    <span class="hljs-keyword">continue</span>;<br><br>                <span class="hljs-keyword">case</span> SelectStrategy.BUSY_WAIT:<br>                    <span class="hljs-comment">// fall-through to SELECT since the busy-wait is not supported with NIO</span><br><br>                <span class="hljs-keyword">case</span> SelectStrategy.SELECT:<br>                    <span class="hljs-built_in">long</span> curDeadlineNanos = nextScheduledTaskDeadlineNanos();<br>                    <span class="hljs-keyword">if</span> (curDeadlineNanos == <span class="hljs-number">-1L</span>) &#123;<br>                        curDeadlineNanos = NONE; <span class="hljs-comment">// nothing on the calendar</span><br>                    &#125;<br>                    nextWakeupNanos.<span class="hljs-keyword">set</span>(curDeadlineNanos);<br>                    <span class="hljs-keyword">try</span> &#123;<br>                        <span class="hljs-keyword">if</span> (!hasTasks()) &#123;<br>                            strategy = <span class="hljs-keyword">select</span>(curDeadlineNanos);<br>                        &#125;<br>                    &#125; <span class="hljs-keyword">finally</span> &#123;<br>                        <span class="hljs-comment">// This update is just to help block unnecessary selector wakeups</span><br>                        <span class="hljs-comment">// so use of lazySet is ok (no race condition)</span><br>                        nextWakeupNanos.lazySet(AWAKE);<br>                    &#125;<br>                    <span class="hljs-comment">// fall through</span><br>                <span class="hljs-literal">default</span>:<br>                &#125;<br>       		&#125;<br>    	&#125;<br>	&#125; <br></code></pre></td></tr></table></figure>

<h3 id=""><a href="#" class="headerlink" title=""></a></h3><h4 id="唤醒"><a href="#唤醒" class="headerlink" title="唤醒"></a>唤醒</h4><p>NioEvnetLoop需要IO事件、普通任务以及定时任务，任务在run方法的for循环中</p>
<figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs aspectj"><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;<br>    <span class="hljs-keyword">int</span> selectCnt = <span class="hljs-number">0</span>;<br>    <span class="hljs-comment">// 死循环，不断地从任务队列中获取各种任务来执行</span><br>    <span class="hljs-keyword">for</span> (;;) &#123;<br>      	<span class="hljs-comment">// 执行各种任务</span><br>   		...<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>中被执行，但<strong>该循环不会空转，执行到某些代码时，会被阻塞</strong></p>
<p>run方法中有SELECT分支</p>
<figure class="highlight lasso"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs lasso"><span class="hljs-keyword">case</span> SelectStrategy.<span class="hljs-keyword">SELECT</span>:<br>	long curDeadlineNanos = nextScheduledTaskDeadlineNanos();<br>	<span class="hljs-keyword">if</span> (curDeadlineNanos == <span class="hljs-number">-1</span>L) &#123;<br>        curDeadlineNanos = <span class="hljs-literal">NONE</span>; <span class="hljs-comment">// nothing on the calendar</span><br>    &#125;<br>	nextWakeupNanos.<span class="hljs-built_in">set</span>(curDeadlineNanos);<br>	try &#123;<br>    	<span class="hljs-keyword">if</span> (!hasTasks()) &#123;<br>            <span class="hljs-comment">// 执行select方法</span><br>            strategy = <span class="hljs-keyword">select</span>(curDeadlineNanos);<br>        &#125;<br>    &#125;<br>	<span class="hljs-params">...</span> <br></code></pre></td></tr></table></figure>

<p>会执行NioEvnetLoop的<code>select</code>方法，<strong>该方法内部会根据情况，执行selector的有参和无参的select方法</strong></p>
<figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs aspectj"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">select</span><span class="hljs-params">(<span class="hljs-keyword">long</span> deadlineNanos)</span> <span class="hljs-keyword">throws</span> IOException </span>&#123;<br>    <span class="hljs-comment">// 如果没有指定阻塞事件，就调用select()</span><br>    <span class="hljs-keyword">if</span> (deadlineNanos == NONE) &#123;<br>        <span class="hljs-function"><span class="hljs-keyword">return</span> selector.<span class="hljs-title">select</span><span class="hljs-params">()</span></span>;<br>    &#125;<br>    <span class="hljs-comment">// 否则调用select(timeoutMillis)，指定时间内未发生事件就停止阻塞</span><br>    <span class="hljs-comment">// Timeout will only be 0 if deadline is within 5 microsecs</span><br>    <span class="hljs-keyword">long</span> timeoutMillis = deadlineToDelayNanos(deadlineNanos + <span class="hljs-number">995000</span>L) / <span class="hljs-number">1000000</span>L;<br>    <span class="hljs-keyword">return</span> timeoutMillis &lt;= <span class="hljs-number">0</span> ? selector.selectNow() : selector.select(timeoutMillis);<br>&#125; <br></code></pre></td></tr></table></figure>

<p>但需要注意的是，**<code>select</code>方法是会阻塞线程的，当没有IO事件，但有其他任务需要执行时，需要唤醒线程**</p>
<p>唤醒是通过execute最后的if代码块来完成的</p>
<figure class="highlight gcode"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs gcode"><span class="hljs-comment">// 有任务需要被执行时，唤醒阻塞的NIO线程</span><br><span class="hljs-keyword">if</span> <span class="hljs-comment">(!addTaskWakesUp &amp;&amp; immediate)</span> &#123;<br>    wakeup<span class="hljs-comment">(inEventLoop)</span>;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><code>NioEventLoop.wakeup</code>唤醒被selector.select方法阻塞的NIO线程</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">protected</span> <span class="hljs-built_in">void</span> <span class="hljs-function"><span class="hljs-title">wakeup</span>(<span class="hljs-params"><span class="hljs-built_in">boolean</span> inEventLoop</span>)</span> &#123;<br>    <span class="hljs-comment">// 只有当其他线程给当前NIO线程提交任务时（如执行execute），才会被唤醒</span><br>    <span class="hljs-comment">// 通过AtomicLong进行更新，保证每次只能有一个线程唤醒成功</span><br>    <span class="hljs-keyword">if</span> (!inEventLoop &amp;&amp; nextWakeupNanos.getAndSet(AWAKE) != AWAKE) &#123;<br>        <span class="hljs-comment">// 唤醒被selector.select方法阻塞的NIO线程</span><br>        selector.wakeup();<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>唤醒时需要进行两个判断</p>
<ul>
<li><p>判断提交任务的</p>
<p>是否为NIO线程</p>
<ul>
<li>若是其他线程，才能唤醒NIO线程</li>
<li>若是NIO线程自己，则不能唤醒</li>
</ul>
</li>
<li><p>通过<strong>AtomicLong</strong>保证有多个线程同时提交任务时，只有一个线程能够唤醒NIO线程</p>
</li>
</ul>
<h4 id="SELECT分支"><a href="#SELECT分支" class="headerlink" title="SELECT分支"></a>SELECT分支</h4><p>run方法的switch语句有多条分支，具体执行分支的代码由strategy变量控制</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-built_in">int</span> strategy = selectStrategy.calculate<span class="hljs-constructor">Strategy(<span class="hljs-params">selectNowSupplier</span>, <span class="hljs-params">hasTasks</span>()</span>);<br>switch (strategy) &#123;<span class="hljs-operator"></span><br><span class="hljs-operator">	...</span><br><span class="hljs-operator"></span>&#125; <br></code></pre></td></tr></table></figure>

<p>strategy的值由<code>calculateStrategy</code>方法确定</p>
<figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs aspectj"><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">calculateStrategy</span><span class="hljs-params">(IntSupplier selectSupplier, <span class="hljs-keyword">boolean</span> hasTasks)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;<br>    <span class="hljs-comment">// selectSupplier.get() 底层是 selector.selectNow();</span><br>    <span class="hljs-keyword">return</span> hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>该方法会<strong>根据hasTaks变量判断任务队列中是否有任务</strong></p>
<ul>
<li><p>若有任务，则<strong>通过selectSupplier获得strategy的值</strong></p>
<ul>
<li><p><strong>get方法会selectNow方法，顺便拿到IO事件</strong></p>
<figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs aspectj"><span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> IntSupplier selectNowSupplier = <span class="hljs-keyword">new</span> IntSupplier() &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">get</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;<br>        <span class="hljs-keyword">return</span> NioEventLoop.<span class="hljs-keyword">this</span>.selectNow();<br>    &#125;<br>&#125;;<br><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">selectNow</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException </span>&#123;<br>    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.selector.selectNow();<br>&#125; <br></code></pre></td></tr></table></figure></li>
</ul>
</li>
<li><p>若没有任务，就会进入SELECT分支</p>
</li>
</ul>
<p>也就说，<strong>当任务队列中没有任务时，才会进入SELECT分支，让NIO线程阻塞，而不是空转。若有任务，则会通过<code>get</code>方法调用<code>selector.selectNow</code>方法，顺便拿到IO事件</strong></p>
<h3 id="Java-NIO空轮询BUG"><a href="#Java-NIO空轮询BUG" class="headerlink" title="Java NIO空轮询BUG"></a>Java NIO空轮询BUG</h3><p>Java NIO空轮询BUG也就是JavaNIO在Linux系统下的epoll空轮询问题</p>
<p><strong>在NioEventLoop中，因为run方法中存在一个死循环，需要通过selector.select方法来阻塞线程。但是select方法因为BUG，可能无法阻塞线程，导致循环一直执行，使得CPU负载升高</strong></p>
<figure class="highlight lasso"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs lasso">@Override<br><span class="hljs-keyword">protected</span> <span class="hljs-literal">void</span> run() &#123;<br>    <span class="hljs-params">...</span><br>    for(;;)&#123;<br>        <span class="hljs-params">...</span><br>        <span class="hljs-comment">// 可能发生空轮询，无法阻塞NIO线程</span><br>        strategy = <span class="hljs-keyword">select</span>(curDeadlineNanos);  <br>        <span class="hljs-params">...</span>     <br>    <br>     	<span class="hljs-keyword">if</span>(<span class="hljs-params">...</span>) &#123;<br>			<span class="hljs-params">...</span><br>     	&#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (unexpectedSelectorWakeup(selectCnt) )&#123;<br>            <span class="hljs-comment">// 通过unexpectedSelectorWakeup方法中的rebuildSelector重建selector</span><br>            <span class="hljs-comment">// 并将selectCnt重置为0</span><br>            selectCnt = <span class="hljs-number">0</span>;<br>        &#125;<br>	&#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>Netty中通过<code>selectCnt</code>变量来检测<code>select</code>方法是否发生空轮询BUG</strong></p>
<p>若发生空轮询BUG，那么selectCnt的值会<strong>增长是十分迅速</strong>。当<code>selectCnt</code>的值大于等于<code>SELECTOR_AUTO_REBUILD_THRESHOLD</code>（默认512）时，Netty则判断其出现了空轮询BUG，进行如下处理</p>
<figure class="highlight less"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs less"><span class="hljs-selector-tag">if</span> (SELECTOR_AUTO_REBUILD_THRESHOLD &gt; <span class="hljs-number">0</span> &amp;&amp; selectCnt &gt;= SELECTOR_AUTO_REBUILD_THRESHOLD) &#123;<br>    <span class="hljs-comment">// The selector returned prematurely many times in a row.</span><br>    <span class="hljs-comment">// Rebuild the selector to work around the problem.</span><br>    <span class="hljs-selector-tag">logger</span><span class="hljs-selector-class">.warn</span>(<span class="hljs-string">&quot;Selector.select() returned prematurely &#123;&#125; times in a row; rebuilding Selector &#123;&#125;.&quot;</span>,selectCnt, selector);<br>    <span class="hljs-comment">// 重建selector，将原selector的配置信息传给新selector</span><br>    <span class="hljs-comment">// 再用新selector覆盖旧selector</span><br>    <span class="hljs-selector-tag">rebuildSelector</span>();<br>    <span class="hljs-selector-tag">return</span> <span class="hljs-selector-tag">true</span>;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>通过<code>rebuildSelector</code>方法重建selector，将原selector的配置信息传给新selector，再用新selector覆盖旧selector。同时将selectCnt的值设置为0</strong></p>
<h3 id="ioRatio"><a href="#ioRatio" class="headerlink" title="ioRatio"></a>ioRatio</h3><p>NioEventLoop可以处理IO事件和其他任务。不同的操作所耗费的时间是不同的，<strong>想要控制NioEventLoop处理IO事件花费时间占执行所有操作的总时间的比例，需要通过ioRatio来控制</strong></p>
<p><strong>NioEventLoop.run方法</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-comment">// 处理IO事件时间比例，默认为50%</span><br>final <span class="hljs-built_in">int</span> ioRatio = this.ioRatio;<br><br><span class="hljs-comment">// 如果IO事件时间比例设置为100%</span><br><span class="hljs-keyword">if</span> (ioRatio<span class="hljs-operator"> == </span><span class="hljs-number">100</span>) &#123;<br>    <span class="hljs-keyword">try</span> &#123;<br>        <span class="hljs-comment">// 如果需要去处理IO事件</span><br>        <span class="hljs-keyword">if</span> (strategy &gt; <span class="hljs-number">0</span>) &#123;<br>            <span class="hljs-comment">// 先处理IO事件</span><br>            process<span class="hljs-constructor">SelectedKeys()</span>;<br>        &#125;<br>    &#125; finally &#123;<br>        <span class="hljs-comment">// Ensure we always run tasks.</span><br>        <span class="hljs-comment">// 剩下的时间都去处理普通任务和定时任务</span><br>        ranTasks = run<span class="hljs-constructor">AllTasks()</span>;<br>    &#125;<br>&#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (strategy &gt; <span class="hljs-number">0</span>) &#123; <span class="hljs-comment">// 如果需要去处理IO事件</span><br>    <span class="hljs-comment">// 记录处理IO事件前的时间</span><br>    final long ioStartTime = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>nano<span class="hljs-constructor">Time()</span>;<br>    <span class="hljs-keyword">try</span> &#123;<br>        <span class="hljs-comment">// 去处理IO事件</span><br>        process<span class="hljs-constructor">SelectedKeys()</span>;<br>    &#125; finally &#123;<br>        <span class="hljs-comment">// Ensure we always run tasks.</span><br>        <span class="hljs-comment">// ioTime为处理IO事件耗费的事件</span><br>        final long ioTime = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>nano<span class="hljs-constructor">Time()</span> - ioStartTime;<br>        <span class="hljs-comment">// 计算出处理其他任务的事件</span><br>        <span class="hljs-comment">// 超过设定的时间后，将会停止任务的执行，会在下一次循环中再继续执行</span><br>        ranTasks = run<span class="hljs-constructor">AllTasks(<span class="hljs-params">ioTime</span> <span class="hljs-operator">*</span> (100 - <span class="hljs-params">ioRatio</span>)</span><span class="hljs-operator"> / </span>ioRatio);<br>    &#125;<br>&#125; <span class="hljs-keyword">else</span> &#123; <span class="hljs-comment">// 没有IO事件需要处理</span><br>    <span class="hljs-comment">// This will run the minimum number of tasks</span><br>    <span class="hljs-comment">// 直接处理普通和定时任务</span><br>    ranTasks = run<span class="hljs-constructor">AllTasks(0)</span>; <br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>通过ioRatio控制各个任务执行的过程如下</strong></p>
<ul>
<li><p>判断ioRatio是否为100</p>
<ul>
<li><p>若是，判断是否需要处理IO事件（strategy&gt;0）</p>
<ul>
<li>若需要处理IO事件，则先处理IO事件</li>
</ul>
</li>
<li><p>若否（或IO事件已经处理完毕），<strong>接下来去执行所有的普通任务和定时任务，直到所有任务都被处理完</strong></p>
<figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs abnf">// 没有指定执行任务的时间<br><span class="hljs-attribute">ranTasks</span> = runAllTasks()<span class="hljs-comment">;Copy</span><br></code></pre></td></tr></table></figure></li>
</ul>
</li>
<li><p>若ioRatio不为100</p>
<ul>
<li><p>先去处理IO事件，<strong>记录处理IO事件所花费的事件保存在ioTime中</strong></p>
</li>
<li><p>接下来去处理其他任务，<strong>根据ioTime与ioRatio计算执行其他任务可用的时间</strong></p>
<figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs awk"><span class="hljs-regexp">//</span> 比如ioTime为<span class="hljs-number">10</span>s，ioRatio为<span class="hljs-number">50</span><br><span class="hljs-regexp">//</span> 那么通过 <span class="hljs-number">10</span>*(<span class="hljs-number">100</span>-<span class="hljs-number">50</span>)/<span class="hljs-number">50</span>=<span class="hljs-number">10</span> 计算出其他任务可用的时间为 <span class="hljs-number">10</span>s<br><span class="hljs-regexp">//</span> 处理IO事件占用的事件总比例为<span class="hljs-number">50</span>%<br>ranTasks = runAllTasks(ioTime * (<span class="hljs-number">100</span> - ioRatio) / ioRatio);Copy<br></code></pre></td></tr></table></figure></li>
<li><p>执行其他任务一旦超过可用时间，则会停止执行，在下一次循环中再继续执行</p>
</li>
</ul>
</li>
<li><p>若没有IO事件需要处理，则去执行<strong>最少数量</strong>的普通任务和定时任务</p>
<figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs abnf">// 运行最少数量的任务<br><span class="hljs-attribute">ranTasks</span> = runAllTasks(<span class="hljs-number">0</span>)<span class="hljs-comment">;Copy</span><br></code></pre></td></tr></table></figure></li>
</ul>
<h3 id="处理事件"><a href="#处理事件" class="headerlink" title="处理事件"></a>处理事件</h3><p>IO事件是通过<code>NioEventLoop.processSelectedKeys()</code>方法处理的</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-keyword">private</span> void process<span class="hljs-constructor">SelectedKeys()</span> &#123;<br>    <span class="hljs-comment">// 如果selectedKeys是基于数组的</span><br>    <span class="hljs-comment">// 一般情况下都走这个分支</span><br>    <span class="hljs-keyword">if</span> (selectedKeys != null) &#123;<br>        <span class="hljs-comment">// 处理各种IO事件</span><br>        process<span class="hljs-constructor">SelectedKeysOptimized()</span>;<br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>        process<span class="hljs-constructor">SelectedKeysPlain(<span class="hljs-params">selector</span>.<span class="hljs-params">selectedKeys</span>()</span>);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>processSelectedKeysOptimized方法</p>
<figure class="highlight smali"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs smali">private void processSelectedKeysOptimized() &#123;<br>    for (int i = 0; i &lt; selectedKeys.size; ++i) &#123;<br>        // 拿到SelectionKeyec<br>       <span class="hljs-keyword"> final</span> SelectionKey k = selectedKeys.keys[i];<br>        // null out entry in the<span class="hljs-built_in"> array </span>to allow to have it GC&#x27;ed once the Channel close<br>        // See https://github.com/netty/netty/issues/2363<br>        selectedKeys.keys[i] = null;<br><br>        // 获取SelectionKey上的附件，即NioServerSocketChannel<br>       <span class="hljs-keyword"> final</span> Object a = k.attachment();<br><br>       <span class="hljs-built_in"> if </span>(a instanceof AbstractNioChannel) &#123;<br>            // 处理事件，传入附件NioServerSocketChannel<br>            processSelectedKey(k, (AbstractNioChannel) a);<br>        &#125; else &#123;<br>            @SuppressWarnings(<span class="hljs-string">&quot;unchecked&quot;</span>)<br>            NioTask&lt;SelectableChannel&gt; task = (NioTask&lt;SelectableChannel&gt;) a;<br>            processSelectedKey(k, task);<br>        &#125;<br><br>       <span class="hljs-built_in"> if </span>(needsToSelectAgain) &#123;<br>            // null out entries in the<span class="hljs-built_in"> array </span>to allow to have it GC&#x27;ed once the Channel close<br>            // See https://github.com/netty/netty/issues/2363<br>            selectedKeys.reset(i + 1);<br><br>            selectAgain();<br>            i = -1;<br>        &#125;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>该方法中通过fori的方法，遍历基于数组的SelectedKey，通过</p>
<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs gradle"><span class="hljs-keyword">final</span> SelectionKey k = selectedKeys.keys[i];<span class="hljs-keyword">Copy</span><br></code></pre></td></tr></table></figure>

<p>获取到SelectionKey，<strong>然后获取其再Register时添加的附件<code>NioServerSocketChannel</code></strong></p>
<figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs dart"><span class="hljs-comment">// 获取SelectionKey上的附件，即NioServerSocketChannel</span><br><span class="hljs-keyword">final</span> <span class="hljs-built_in">Object</span> a = k.attachment();Copy<br></code></pre></td></tr></table></figure>

<p>如果附件继承自AbstractNioChannel，则会调用</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-comment">// 处理事件，传入附件NioServerSocketChannel</span><br>process<span class="hljs-constructor">SelectedKey(<span class="hljs-params">k</span>, (AbstractNioChannel)</span> a);Copy<br></code></pre></td></tr></table></figure>

<p>去处理各个事件</p>
<p><strong>真正处理各种事件的方法<code>processSelectedKey</code></strong></p>
<p>获取SelectionKey的事件，然后进行相应处理</p>
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><code class="hljs csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">processSelectedKey</span>(<span class="hljs-params">SelectionKey k, AbstractNioChannel ch</span>)</span> &#123;<br>    final AbstractNioChannel.NioUnsafe <span class="hljs-keyword">unsafe</span> = ch.<span class="hljs-keyword">unsafe</span>();<br>    <span class="hljs-keyword">if</span> (!k.isValid()) &#123;<br>        final EventLoop eventLoop;<br>        <span class="hljs-keyword">try</span> &#123;<br>            eventLoop = ch.eventLoop();<br>        &#125; catch (Throwable ignored) &#123;<br>            <span class="hljs-comment">// If the channel implementation throws an exception because there is no event loop, we ignore this</span><br>            <span class="hljs-comment">// because we are only trying to determine if ch is registered to this event loop and thus has authority</span><br>            <span class="hljs-comment">// to close ch.</span><br>            <span class="hljs-keyword">return</span>;<br>        &#125;<br>        <span class="hljs-comment">// Only close ch if ch is still registered to this EventLoop. ch could have deregistered from the event loop</span><br>        <span class="hljs-comment">// and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is</span><br>        <span class="hljs-comment">// still healthy and should not be closed.</span><br>        <span class="hljs-comment">// See https://github.com/netty/netty/issues/5125</span><br>        <span class="hljs-keyword">if</span> (eventLoop == <span class="hljs-keyword">this</span>) &#123;<br>            <span class="hljs-comment">// close the channel if the key is not valid anymore</span><br>            <span class="hljs-keyword">unsafe</span>.close(<span class="hljs-keyword">unsafe</span>.voidPromise());<br>        &#125;<br>        <span class="hljs-keyword">return</span>;<br>    &#125;<br><br>    <span class="hljs-keyword">try</span> &#123;<br>        <span class="hljs-built_in">int</span> readyOps = k.readyOps();<br>        <span class="hljs-comment">// We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise</span><br>        <span class="hljs-comment">// the NIO JDK channel implementation may throw a NotYetConnectedException.</span><br>        <span class="hljs-keyword">if</span> ((readyOps &amp; SelectionKey.OP_CONNECT) != <span class="hljs-number">0</span>) &#123;<br>            <span class="hljs-comment">// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking</span><br>            <span class="hljs-comment">// See https://github.com/netty/netty/issues/924</span><br>            <span class="hljs-built_in">int</span> ops = k.interestOps();<br>            ops &amp;= ~SelectionKey.OP_CONNECT;<br>            k.interestOps(ops);<br><br>            <span class="hljs-keyword">unsafe</span>.finishConnect();<br>        &#125;<br><br>        <span class="hljs-comment">// Process OP_WRITE first as we may be able to write some queued buffers and so free memory.</span><br>        <span class="hljs-keyword">if</span> ((readyOps &amp; SelectionKey.OP_WRITE) != <span class="hljs-number">0</span>) &#123;<br>            <span class="hljs-comment">// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write</span><br>            ch.<span class="hljs-keyword">unsafe</span>().forceFlush();<br>        &#125;<br><br>        <span class="hljs-comment">// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead</span><br>        <span class="hljs-comment">// to a spin loop</span><br>        <span class="hljs-keyword">if</span> ((readyOps &amp; (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != <span class="hljs-number">0</span> || readyOps == <span class="hljs-number">0</span>) &#123;<br>            <span class="hljs-keyword">unsafe</span>.read();<br>        &#125;<br>    &#125; catch (CancelledKeyException ignored) &#123;<br>        <span class="hljs-keyword">unsafe</span>.close(<span class="hljs-keyword">unsafe</span>.voidPromise());<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h2 id="3、Accept剖析"><a href="#3、Accept剖析" class="headerlink" title="3、Accept剖析"></a>3、Accept剖析</h2><h3 id="NIO中处理Accept事件流程"><a href="#NIO中处理Accept事件流程" class="headerlink" title="NIO中处理Accept事件流程"></a>NIO中处理Accept事件流程</h3><p>NIO中处理Accept事件主要有以下六步</p>
<ul>
<li>selector.select()阻塞线程，直到事件发生</li>
<li>遍历selectionKeys</li>
<li>获取一个key，判断事件类型是否为Accept</li>
</ul>
<hr>
<ul>
<li>创建SocketChannel，设置为非阻塞</li>
<li>将SocketChannel注册到selector中</li>
<li>关注selectionKeys的read事件</li>
</ul>
<p>代码如下</p>
<figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs awk"><span class="hljs-regexp">//</span> 阻塞直到事件发生<br>selector.select();<br><br>Iterator&lt;SelectionKey&gt; iter = selector.selectionKeys().iterator();<br><span class="hljs-keyword">while</span> (iter.hasNext()) &#123;    <br>    <span class="hljs-regexp">//</span> 拿到一个事件<br>    SelectionKey key = iter.<span class="hljs-keyword">next</span>();<br>    <br>    <span class="hljs-regexp">//</span> 如果是 accept 事件<br>    <span class="hljs-keyword">if</span> (key.isAcceptable()) &#123;<br>        <br>        <span class="hljs-regexp">//</span> 执行accept，获得SocketChannel<br>        SocketChannel channel = serverSocketChannel.accept();<br>        channel.configureBlocking(false);<br>        <br>        <span class="hljs-regexp">//</span> 将SocketChannel注册到selector中，并关注read事件<br>        channel.register(selector, SelectionKey.OP_READ);<br>    &#125;<br>    <span class="hljs-regexp">//</span> ...<br>&#125; <br></code></pre></td></tr></table></figure>

<p><strong>其中前三步，在NioEventLoop剖析中已经分析过了，所以接下来主要分析后三步</strong></p>
<h3 id="SocketChannel的创建与注册"><a href="#SocketChannel的创建与注册" class="headerlink" title="SocketChannel的创建与注册"></a>SocketChannel的创建与注册</h3><p>发生Accept事件后，会执行<code>NioEventLoop.run</code>方法的如下if分支</p>
<figure class="highlight lisp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs lisp">if ((<span class="hljs-name">readyOps</span> &amp; (<span class="hljs-name">SelectionKey</span>.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == <span class="hljs-number">0</span>) &#123;<br>	unsafe.read()<span class="hljs-comment">;</span><br>&#125; <br></code></pre></td></tr></table></figure>

<p><code>NioMessageUnsafe.read</code>方法</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public void read<span class="hljs-literal">()</span> &#123;<span class="hljs-operator"></span><br><span class="hljs-operator"></span><br><span class="hljs-operator">    ...</span><br><span class="hljs-operator">    </span><br><span class="hljs-operator">    </span><span class="hljs-keyword">try</span> &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-keyword">do</span> &#123;<br>				<span class="hljs-comment">// doReadMessages中执行了accept获得了SocketChannel</span><br>                <span class="hljs-comment">// 并创建NioSocketChannel作为消息放入readBuf</span><br>                <span class="hljs-comment">// readBuf是一个ArrayList用来缓存消息</span><br>                <span class="hljs-comment">// private final List&lt;Object&gt; readBuf = new ArrayList&lt;Object&gt;();</span><br>                <span class="hljs-built_in">int</span> localRead = <span class="hljs-keyword">do</span><span class="hljs-constructor">ReadMessages(<span class="hljs-params">readBuf</span>)</span>;<span class="hljs-operator"></span><br><span class="hljs-operator">                </span><br><span class="hljs-operator">                ...</span><br><span class="hljs-operator">                </span><br><span class="hljs-operator">				</span><span class="hljs-comment">// localRead值为1，就一条消息，即接收一个客户端连接</span><br>                allocHandle.inc<span class="hljs-constructor">MessagesRead(<span class="hljs-params">localRead</span>)</span>;<br>            &#125; <span class="hljs-keyword">while</span> (allocHandle.continue<span class="hljs-constructor">Reading()</span>);<br>        &#125; catch (Throwable t) &#123;<br>            <span class="hljs-keyword">exception</span> = t;<br>        &#125;<br><br>        <span class="hljs-built_in">int</span> size = readBuf.size<span class="hljs-literal">()</span>;<br>        <span class="hljs-keyword">for</span> (<span class="hljs-built_in">int</span> i = <span class="hljs-number">0</span>; i &lt; size; i ++) &#123;<br>            readPending = <span class="hljs-literal">false</span>;<br>            <span class="hljs-comment">// 触发read事件，让pipeline上的handler处理</span><br>            <span class="hljs-comment">// ServerBootstrapAcceptor.channelRead</span><br>            pipeline.fire<span class="hljs-constructor">ChannelRead(<span class="hljs-params">readBuf</span>.<span class="hljs-params">get</span>(<span class="hljs-params">i</span>)</span>);<br>        &#125;<span class="hljs-operator"></span><br><span class="hljs-operator">        </span><br><span class="hljs-operator">        ...</span><br><span class="hljs-operator">        </span><br><span class="hljs-operator">    </span>&#125; finally &#123;<br>        <span class="hljs-keyword">if</span> (!readPending<span class="hljs-operator"> &amp;&amp; </span>!config.is<span class="hljs-constructor">AutoRead()</span>) &#123;<br>            remove<span class="hljs-constructor">ReadOp()</span>;<br>        &#125;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><code>NioSocketChannel.doReadMessages</code>方法</p>
<p>该方法中处理accpet事件，<strong>获得SocketChannel</strong>，同时<strong>创建了NioSocketChannel</strong>，作为消息放在了readBuf中</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">@Override<br>protected <span class="hljs-built_in">int</span> <span class="hljs-keyword">do</span><span class="hljs-constructor">ReadMessages(List&lt;Object&gt; <span class="hljs-params">buf</span>)</span> throws Exception &#123;<br>    <span class="hljs-comment">// 处理accpet事件，获得SocketChannel</span><br>    SocketChannel ch = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">SocketUtils</span>.</span></span>accept(java<span class="hljs-constructor">Channel()</span>);<br><br>    <span class="hljs-keyword">try</span> &#123;<br>        <span class="hljs-keyword">if</span> (ch != null) &#123;<br>            <span class="hljs-comment">// 创建了NioSocketChannel，作为消息放在了readBuf中</span><br>            buf.add(<span class="hljs-keyword">new</span> <span class="hljs-constructor">NioSocketChannel(<span class="hljs-params">this</span>, <span class="hljs-params">ch</span>)</span>);<br>            return <span class="hljs-number">1</span>;<br>        &#125;<br>    &#125; catch (Throwable t) &#123;<span class="hljs-operator"></span><br><span class="hljs-operator">       ...</span><br><span class="hljs-operator">    </span>&#125;<br><br>    return <span class="hljs-number">0</span>;<br>&#125; <br><span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">ServerBootstrapAcceptor</span>.</span></span>channelRead<br>public void channel<span class="hljs-constructor">Read(ChannelHandlerContext <span class="hljs-params">ctx</span>, Object <span class="hljs-params">msg</span>)</span> &#123;<br>    <span class="hljs-comment">// 这时的msg是NioSocketChannel</span><br>    final Channel child = (Channel) msg;<br><br>    <span class="hljs-comment">// NioSocketChannel添加childHandler，即初始化器</span><br>    child.pipeline<span class="hljs-literal">()</span>.add<span class="hljs-constructor">Last(<span class="hljs-params">childHandler</span>)</span>;<br><br>    <span class="hljs-comment">// 设置选项</span><br>    set<span class="hljs-constructor">ChannelOptions(<span class="hljs-params">child</span>, <span class="hljs-params">childOptions</span>, <span class="hljs-params">logger</span>)</span>;<br><br>    <span class="hljs-keyword">for</span> (Entry&lt;AttributeKey&lt;?&gt;, Object&gt; e: childAttrs) &#123;<br>        child.attr((AttributeKey&lt;Object&gt;) e.get<span class="hljs-constructor">Key()</span>).set(e.get<span class="hljs-constructor">Value()</span>);<br>    &#125;<br><br>    <span class="hljs-keyword">try</span> &#123;<br>        <span class="hljs-comment">// 注册 NioSocketChannel到nio worker线程，接下来的处理也移交至nio worker线程</span><br>        childGroup.register(child).add<span class="hljs-constructor">Listener(<span class="hljs-params">new</span> ChannelFutureListener()</span> &#123;<br>            @Override<br>            public void operation<span class="hljs-constructor">Complete(ChannelFuture <span class="hljs-params">future</span>)</span> throws Exception &#123;<br>                <span class="hljs-keyword">if</span> (!future.is<span class="hljs-constructor">Success()</span>) &#123;<br>                    force<span class="hljs-constructor">Close(<span class="hljs-params">child</span>, <span class="hljs-params">future</span>.<span class="hljs-params">cause</span>()</span>);<br>                &#125;<br>            &#125;<br>        &#125;);<br>    &#125; catch (Throwable t) &#123;<br>        force<span class="hljs-constructor">Close(<span class="hljs-params">child</span>, <span class="hljs-params">t</span>)</span>;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>通过<code>AbstractUnsafe.register</code> 方法，将SocketChannel注册到了Selector中，<strong>过程与启动流程中的Register过程类似</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">public final void register(EventLoop eventLoop, final ChannelPromise promise) &#123;<span class="hljs-operator"></span><br><span class="hljs-operator">    </span><br><span class="hljs-operator">    ...</span><br><span class="hljs-operator"></span><br><span class="hljs-operator">    </span><span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">AbstractChannel</span>.</span></span>this.eventLoop = eventLoop;<br><br>    <span class="hljs-keyword">if</span> (eventLoop.<span class="hljs-keyword">in</span><span class="hljs-constructor">EventLoop()</span>) &#123;<br>        register0(promise);<br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">// 这行代码完成的是nio boss -&gt; nio worker线程的切换</span><br>            eventLoop.execute(<span class="hljs-keyword">new</span> <span class="hljs-constructor">Runnable()</span> &#123;<br>                @Override<br>                public void run<span class="hljs-literal">()</span> &#123;<br>                    <span class="hljs-comment">// 真正的注册操作</span><br>                    register0(promise);<br>                &#125;<br>            &#125;);<br>        &#125; catch (Throwable t) &#123;<span class="hljs-operator"></span><br><span class="hljs-operator">            ...</span><br><span class="hljs-operator">        </span>&#125;<br>    &#125;<br>&#125; <br><span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">AbstractChannel</span>.</span><span class="hljs-module"><span class="hljs-identifier">AbstractUnsafe</span>.</span></span>register0<br><span class="hljs-keyword">private</span> void register0(ChannelPromise promise) &#123;<br>    <span class="hljs-keyword">try</span> &#123;<span class="hljs-operator"></span><br><span class="hljs-operator">        </span><br><span class="hljs-operator">        ...</span><br><span class="hljs-operator">            </span><br><span class="hljs-operator">        </span><span class="hljs-comment">// 该方法将SocketChannel注册到Selector中</span><br>        <span class="hljs-keyword">do</span><span class="hljs-constructor">Register()</span>;<br>        <br>        <span class="hljs-comment">// 执行初始化器，执行前 pipeline 中只有 head -&gt; 初始化器 -&gt; tail</span><br>        pipeline.invoke<span class="hljs-constructor">HandlerAddedIfNeeded()</span>;<br>        <span class="hljs-comment">// 执行后就是 head -&gt; logging handler -&gt; my handler -&gt; tail</span><br><br>        safe<span class="hljs-constructor">SetSuccess(<span class="hljs-params">promise</span>)</span>;<br>        pipeline.fire<span class="hljs-constructor">ChannelRegistered()</span>;<br>        <br>        <span class="hljs-keyword">if</span> (is<span class="hljs-constructor">Active()</span>) &#123;<br>            <span class="hljs-keyword">if</span> (firstRegistration) &#123;<br>                <span class="hljs-comment">// 触发pipeline上active事件</span><br>                pipeline.fire<span class="hljs-constructor">ChannelActive()</span>;<br>            &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (config<span class="hljs-literal">()</span>.is<span class="hljs-constructor">AutoRead()</span>) &#123;<br>                <span class="hljs-keyword">begin</span><span class="hljs-constructor">Read()</span>;<br>            &#125;<br>        &#125;<br>    &#125; catch (Throwable t) &#123;<br>        close<span class="hljs-constructor">Forcibly()</span>;<br>        closeFuture.set<span class="hljs-constructor">Closed()</span>;<br>        safe<span class="hljs-constructor">SetFailure(<span class="hljs-params">promise</span>, <span class="hljs-params">t</span>)</span>;<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<p><code>AbstractNioChannel.doRegister</code>将SocketChannel注册到Selector中</p>
<figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs aspectj"><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">doRegister</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;<br>    <span class="hljs-keyword">boolean</span> selected = <span class="hljs-keyword">false</span>;<br>    <span class="hljs-keyword">for</span> (;;) &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">// 将Selector注册到Selector中</span><br>            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), <span class="hljs-number">0</span>, <span class="hljs-keyword">this</span>);<br>            <span class="hljs-keyword">return</span>;<br>        &#125; <span class="hljs-keyword">catch</span> (CancelledKeyException e) &#123;<br>            ...<br>        &#125;<br>    &#125;<br>&#125; <br>HeadContext.channelActive<br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">channelActive</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> </span>&#123;<br>    ctx.fireChannelActive();<br>	<span class="hljs-comment">// 触发read(NioSocketChannel这里read只是为了触发channel的事件注册，还未涉及数据读取)</span><br>    readIfIsAutoRead();<br>&#125; <br></code></pre></td></tr></table></figure>

<p><code>AbstractNioChannel.doBeginRead</code>，通过该方法，SocketChannel关注了read事件</p>
<figure class="highlight angelscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs angelscript"><span class="hljs-keyword">protected</span> <span class="hljs-built_in">void</span> doBeginRead() throws Exception &#123;<br>    <span class="hljs-comment">// Channel.read() or ChannelHandlerContext.read() was called</span><br>    <span class="hljs-keyword">final</span> SelectionKey selectionKey = <span class="hljs-keyword">this</span>.selectionKey;<br>    <span class="hljs-keyword">if</span> (!selectionKey.isValid()) &#123;<br>        <span class="hljs-keyword">return</span>;<br>    &#125;<br><br>    readPending = <span class="hljs-literal">true</span>;<br>	<span class="hljs-comment">// 这时候 interestOps是0</span><br>    <span class="hljs-keyword">final</span> <span class="hljs-built_in">int</span> <span class="hljs-built_in">int</span>erestOps = selectionKey.<span class="hljs-built_in">int</span>erestOps();<br>    <span class="hljs-keyword">if</span> ((<span class="hljs-built_in">int</span>erestOps &amp; readInterestOp) == <span class="hljs-number">0</span>) &#123;<br>        <span class="hljs-comment">// 关注read事件</span><br>        selectionKey.<span class="hljs-built_in">int</span>erestOps(<span class="hljs-built_in">int</span>erestOps | readInterestOp);<br>    &#125;<br>&#125; <br></code></pre></td></tr></table></figure>

<h2 id="4、Read剖析"><a href="#4、Read剖析" class="headerlink" title="4、Read剖析"></a>4、Read剖析</h2><p>read事件的处理也是在</p>
<figure class="highlight lisp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs lisp">if ((<span class="hljs-name">readyOps</span> &amp; (<span class="hljs-name">SelectionKey</span>.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == <span class="hljs-number">0</span>) &#123;<br>	unsafe.read()<span class="hljs-comment">;</span><br>&#125; <br></code></pre></td></tr></table></figure>

<p>分支中，通过<code>unsafe.read()</code>方法处理的，<strong>不过此处调用的方法在AbstractNioByteChannel.NioByteUnsafe类中</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">java@Override<br>public final void read<span class="hljs-literal">()</span> &#123;<br>    <span class="hljs-comment">// 获得Channel的配置</span><br>    final ChannelConfig config = config<span class="hljs-literal">()</span>;<br>    <span class="hljs-keyword">if</span> (should<span class="hljs-constructor">BreakReadReady(<span class="hljs-params">config</span>)</span>) &#123;<br>        clear<span class="hljs-constructor">ReadPending()</span>;<br>        return;<br>    &#125;<br>    final ChannelPipeline pipeline = pipeline<span class="hljs-literal">()</span>;<br>	<span class="hljs-comment">// 根据配置创建ByteBufAllocator（池化非池化、直接非直接内存）</span><br>	final ByteBufAllocator allocator = config.get<span class="hljs-constructor">Allocator()</span>;<br>    <span class="hljs-comment">// 用来分配 byteBuf，确定单次读取大小</span><br>    final RecvByteBufAllocator.Handle allocHandle = recv<span class="hljs-constructor">BufAllocHandle()</span>;<br>    allocHandle.reset(config);<br><br>    ByteBuf byteBuf = null;<br>    boolean close = <span class="hljs-literal">false</span>;<br>    <span class="hljs-keyword">try</span> &#123;<br>        <span class="hljs-keyword">do</span> &#123;<br>            <span class="hljs-comment">// 创建ByteBuf</span><br>            byteBuf = allocHandle.allocate(allocator);<br>            <span class="hljs-comment">// 读取内容，放入ByteBUf中</span><br>            allocHandle.last<span class="hljs-constructor">BytesRead(<span class="hljs-params">doReadBytes</span>(<span class="hljs-params">byteBuf</span>)</span>);<br>            <span class="hljs-keyword">if</span> (allocHandle.last<span class="hljs-constructor">BytesRead()</span> &lt;= <span class="hljs-number">0</span>) &#123;<br>                byteBuf.release<span class="hljs-literal">()</span>;<br>                byteBuf = null;<br>                close = allocHandle.last<span class="hljs-constructor">BytesRead()</span> &lt; <span class="hljs-number">0</span>;<br>                <span class="hljs-keyword">if</span> (close) &#123;<br>                    readPending = <span class="hljs-literal">false</span>;<br>                &#125;<br>                break;<br>            &#125;<br><br>            allocHandle.inc<span class="hljs-constructor">MessagesRead(1)</span>;<br>            readPending = <span class="hljs-literal">false</span>;<br>            <span class="hljs-comment">// 触发read 事件，让pipeline上的handler处理</span><br>            <span class="hljs-comment">// 这时是处理NioSocketChannel上的handler</span><br>            pipeline.fire<span class="hljs-constructor">ChannelRead(<span class="hljs-params">byteBuf</span>)</span>;<br>            byteBuf = null;<br>        &#125; <br>        <span class="hljs-comment">// 是否要继续循环</span><br>        <span class="hljs-keyword">while</span> (allocHandle.continue<span class="hljs-constructor">Reading()</span>);<br><br>        allocHandle.read<span class="hljs-constructor">Complete()</span>;<br>        <span class="hljs-comment">// 触发 read complete事件</span><br>        pipeline.fire<span class="hljs-constructor">ChannelReadComplete()</span>;<br><br>        <span class="hljs-keyword">if</span> (close) &#123;<br>            close<span class="hljs-constructor">OnRead(<span class="hljs-params">pipeline</span>)</span>;<br>        &#125;<br>    &#125; catch (Throwable t) &#123;<br>        handle<span class="hljs-constructor">ReadException(<span class="hljs-params">pipeline</span>, <span class="hljs-params">byteBuf</span>, <span class="hljs-params">t</span>, <span class="hljs-params">close</span>, <span class="hljs-params">allocHandle</span>)</span>;<br>    &#125; finally &#123;<br>         <span class="hljs-comment">// Check if there is a readPending which was not processed yet.</span><br>         <span class="hljs-comment">// This could be for two reasons:</span><br>         <span class="hljs-comment">// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method</span><br>         <span class="hljs-comment">// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method</span><br>         <span class="hljs-comment">//</span><br>         <span class="hljs-comment">// See https://github.com/netty/netty/issues/2254</span><br>        <span class="hljs-keyword">if</span> (!readPending<span class="hljs-operator"> &amp;&amp; </span>!config.is<span class="hljs-constructor">AutoRead()</span>) &#123;<br>            remove<span class="hljs-constructor">ReadOp()</span>;<br>        &#125;<br>    &#125;<br>&#125; <br><span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">DefaultMaxMessagesRecvByteBufAllocator</span>.</span><span class="hljs-module"><span class="hljs-identifier">MaxMessageHandle</span>.</span></span>continue<span class="hljs-constructor">Reading(<span class="hljs-params">io</span>.<span class="hljs-params">netty</span>.<span class="hljs-params">util</span>.UncheckedBooleanSupplier)</span><br>public boolean continue<span class="hljs-constructor">Reading(UncheckedBooleanSupplier <span class="hljs-params">maybeMoreDataSupplier</span>)</span> &#123;<br>    return <br>           <span class="hljs-comment">// 一般为true</span><br>           config.is<span class="hljs-constructor">AutoRead()</span><span class="hljs-operator"> &amp;&amp;</span><br><span class="hljs-operator">           </span><span class="hljs-comment">// respectMaybeMoreData默认为true</span><br>           <span class="hljs-comment">// maybeMoreDataSupplier的逻辑是如果预期读取字节与实际读取字节相等，返回true</span><br>           (!respectMaybeMoreData<span class="hljs-operator"> || </span>maybeMoreDataSupplier.get<span class="hljs-literal">()</span>)<span class="hljs-operator"> &amp;&amp;</span><br><span class="hljs-operator">           </span><span class="hljs-comment">// 小于最大次数，maxMessagePerRead默认16</span><br>           totalMessages &lt; maxMessagePerRead<span class="hljs-operator"> &amp;&amp;</span><br><span class="hljs-operator">           </span><span class="hljs-comment">// 实际读到了数据</span><br>           totalBytesRead &gt; <span class="hljs-number">0</span>;<br>&#125; <br></code></pre></td></tr></table></figure>

<p>转载自<a target="_blank" rel="noopener" href="https://nyimac.gitee.io/2021/04/25/Netty%E5%9F%BA%E7%A1%80/#Netty">Nyima</a></p>

            </div>
            <hr>
            <div>
              <div class="post-metas mb-3">
                
                  <div class="post-meta mr-3">
                    <i class="iconfont icon-category"></i>
                    
                      <a class="hover-with-bg" href="/xt-blog/categories/Java/">Java</a>
                    
                  </div>
                
                
                  <div class="post-meta">
                    <i class="iconfont icon-tags"></i>
                    
                      <a class="hover-with-bg" href="/xt-blog/tags/Netty/">Netty</a>
                    
                  </div>
                
              </div>
              
              
                <div class="post-prevnext">
                  <article class="post-prev col-6">
                    
                    
                      <a href="/xt-blog/2022/01/30/884-%E4%B8%A4%E5%8F%A5%E8%AF%9D%E4%B8%AD%E7%9A%84%E4%B8%8D%E5%B8%B8%E8%A7%81%E5%8D%95%E8%AF%8D/">
                        <i class="iconfont icon-arrowleft"></i>
                        <span class="hidden-mobile">884_两句话中的不常见单词</span>
                        <span class="visible-mobile">上一篇</span>
                      </a>
                    
                  </article>
                  <article class="post-next col-6">
                    
                    
                      <a href="/xt-blog/2022/01/29/65-%E6%9C%89%E6%95%88%E6%95%B0%E5%AD%97/">
                        <span class="hidden-mobile">65_有效数字</span>
                        <span class="visible-mobile">下一篇</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </article>
                </div>
              
            </div>

            
          </article>
        </div>
      </div>
    </div>
    
      <div class="d-none d-lg-block col-lg-2 toc-container" id="toc-ctn">
        <div id="toc">
  <p class="toc-header"><i class="iconfont icon-list"></i>&nbsp;目录</p>
  <div class="toc-body" id="toc-body"></div>
</div>

      </div>
    
  </div>
</div>

<!-- Custom -->


    

    
      <a id="scroll-top-button" aria-label="TOP" href="#" role="button">
        <i class="iconfont icon-arrowup" aria-hidden="true"></i>
      </a>
    

    
      <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v"
                 for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>
    

    
  </main>

  <footer class="text-center mt-5 py-3">
  <div class="footer-content">
     Peace <i class="iconfont icon-love"></i> Love 
  </div>
  

  

  
</footer>


  <!-- SCRIPTS -->
  
  <script  src="https://cdn.jsdelivr.net/npm/nprogress@0/nprogress.min.js" ></script>
  <link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nprogress@0/nprogress.min.css" />

  <script>
    NProgress.configure({"showSpinner":false,"trickleSpeed":100})
    NProgress.start()
    window.addEventListener('load', function() {
      NProgress.done();
    })
  </script>


<script  src="https://cdn.jsdelivr.net/npm/jquery@3/dist/jquery.min.js" ></script>
<script  src="https://cdn.jsdelivr.net/npm/bootstrap@4/dist/js/bootstrap.min.js" ></script>
<script  src="/xt-blog/js/events.js" ></script>
<script  src="/xt-blog/js/plugins.js" ></script>

<!-- Plugins -->


  <script  src="/xt-blog/js/local-search.js" ></script>



  
    <script  src="/xt-blog/js/img-lazyload.js" ></script>
  



  



  
    <script  src="https://cdn.jsdelivr.net/npm/tocbot@4/dist/tocbot.min.js" ></script>
  
  
    <script  src="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3/dist/jquery.fancybox.min.js" ></script>
  
  
    <script  src="https://cdn.jsdelivr.net/npm/anchor-js@4/anchor.min.js" ></script>
  
  
    <script defer src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js" ></script>
  






  <script  src="https://cdn.jsdelivr.net/npm/typed.js@2/lib/typed.min.js" ></script>
  <script>
    (function (window, document) {
      var typing = Fluid.plugins.typing;
      var title = document.getElementById('subtitle').title;
      
        typing(title);
      
    })(window, document);
  </script>















<!-- 主题的启动项 保持在最底部 -->
<script  src="/xt-blog/js/boot.js" ></script>


</body>
</html>
